Skip to content

HTTP

USER_AGENT_LITERAL = 'User-Agent' module-attribute

The user agent literal.

USER_AGENT = f'{NAME}/{version_info} ({PYTHON}/{python_version_info})' module-attribute

The user agent to use.

This is formatted using:

HEADERS = {USER_AGENT_LITERAL: USER_AGENT} module-attribute

The default headers to use.

Route

Represents routes.

Source code in aoc/http.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@frozen()
class Route:
    """Represents routes."""

    method: str
    """The HTTP method of the route."""

    path: str
    """The path of the route."""

    @classmethod
    def with_parameters(cls, method: str, path: str, **parameters: Any) -> Self:
        """Constructs a route with `path` formatted using `parameters`.

        Arguments:
            method: The HTTP method of the route.
            path: The path template of the route.
            **parameters: The parameters to format `path` with.

        Returns:
            The route constructed.
        """
        return cls(method, path.format_map(parameters))

    @property
    def key(self) -> str:
        """The key of the route."""
        return key(route=self)

method: str instance-attribute

The HTTP method of the route.

path: str instance-attribute

The path of the route.

key: str property

The key of the route.

with_parameters(method: str, path: str, **parameters: Any) -> Self classmethod

Constructs a route with path formatted using parameters.

Parameters:

Name Type Description Default
method str

The HTTP method of the route.

required
path str

The path template of the route.

required
**parameters Any

The parameters to format path with.

{}

Returns:

Type Description
Self

The route constructed.

Source code in aoc/http.py
42
43
44
45
46
47
48
49
50
51
52
53
54
@classmethod
def with_parameters(cls, method: str, path: str, **parameters: Any) -> Self:
    """Constructs a route with `path` formatted using `parameters`.

    Arguments:
        method: The HTTP method of the route.
        path: The path template of the route.
        **parameters: The parameters to format `path` with.

    Returns:
        The route constructed.
    """
    return cls(method, path.format_map(parameters))

HTTPClient

Represents HTTP clients interacting with the Advent of Code servers.

Source code in aoc/http.py
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
@define()
class HTTPClient:
    """Represents HTTP clients interacting with the Advent of Code servers."""

    token: str = field()
    """The token to use."""

    retries: int = field(default=DEFAULT_RETRIES)
    """The amount of retries to use."""

    async def request(
        self,
        method: str,
        path: str,
        payload: Optional[Payload] = None,
        data: Optional[Parameters] = None,
        parameters: Optional[Parameters] = None,
        headers: Optional[Headers] = None,
    ) -> str:
        """Sends requests to the Advent of Code servers.

        This method sends additional data in the request:

        - `cookies`: The [`token`][aoc.http.HTTPClient.token] in the
          [`TOKEN_COOKIE_NAME`][aoc.constants.TOKEN_COOKIE_NAME] cookie.

        - `headers`: The [`HEADERS`][aoc.http.HEADERS].

        Arguments:
            method: The HTTP method to use.
            path: The path to send the request to, relative to [`BASE_URL`][aoc.constants.BASE_URL].
            payload: The payload to send (JSON).
            data: The data to send.
            parameters: The parameters to use.
            headers: The headers to use.

        Returns:
            The response string.

        Raises:
            ClientError: All request attempts failed.
        """
        attempts = self.retries + 1

        error: Optional[ClientError] = None

        session_cookies = {TOKEN_COOKIE_NAME: self.token}
        session_headers = HEADERS
        base_url = BASE_URL

        while attempts:
            try:
                async with ClientSession(
                    base_url=base_url, cookies=session_cookies, headers=session_headers
                ) as session:
                    response = await session.request(
                        method,
                        path,
                        params=parameters,
                        data=data,
                        json=payload,
                        headers=headers,
                    )

                    response.raise_for_status()

            except ClientError as origin:
                error = origin

            else:
                return await response.text()

            attempts -= 1

        if error:
            raise error

        return EMPTY  # pragma: never

    async def request_route(
        self,
        route: Route,
        payload: Optional[Payload] = None,
        data: Optional[Parameters] = None,
        parameters: Optional[Parameters] = None,
        headers: Optional[Headers] = None,
    ) -> str:
        """Sends requests to the Advent of Code servers using routes.

        ```python
        response = await client.request_route(
            route,
            payload=payload,
            data=data,
            parameters=parameters,
            headers=headers,
        )
        ```

        is equivalent to:

        ```python
        response = await client.request(
            route.method,
            route.path,
            payload=payload,
            data=data,
            parameters=parameters,
            headers=headers,
        )
        ```

        See [`request`][aoc.http.HTTPClient.request] for more information.

        Arguments:
            route: The route to send the request to.
            payload: The payload to send (JSON).
            data: The data to send.
            parameters: The parameters to use.
            headers: The headers to use.

        Returns:
            The response string.

        Raises:
            ClientError: All request attempts failed.
        """
        return await self.request(
            route.method,
            route.path,
            payload=payload,
            data=data,
            parameters=parameters,
            headers=headers,
        )

    async def download_data(self, key: Key) -> str:
        """Downloads the data for the problem for the given `key`.

        Arguments:
            key: The key to download the data for.

        Returns:
            The problem data downloaded.

        Raises:
            ClientError: All request attempts failed.
        """
        route = Route.with_parameters(
            GET, "/{year}/day/{day}/input", year=key.year.value, day=key.day.value
        )

        return await self.request_route(route)

    async def submit_answer(self, key: Key, part: Part, answer: Any) -> State:
        """Submits the `answer` for the problem `part` and the given `key`.

        Arguments:
            key: The key of the problem to submit the answer for.
            part: The part of the problem to submit the answer for.
            answer: The answer to submit.

        Returns:
            The state fetched from the response.

        Raises:
            ClientError: All request attempts failed.
        """
        route = Route.with_parameters(
            POST, "/{year}/day/{day}/answer", year=key.year.value, day=key.day.value
        )

        data = {PART: part.value, ANSWER: answer}

        response = await self.request_route(route, data=data)

        return State.match(response)

token: str = field() class-attribute instance-attribute

The token to use.

retries: int = field(default=DEFAULT_RETRIES) class-attribute instance-attribute

The amount of retries to use.

request(method: str, path: str, payload: Optional[Payload] = None, data: Optional[Parameters] = None, parameters: Optional[Parameters] = None, headers: Optional[Headers] = None) -> str async

Sends requests to the Advent of Code servers.

This method sends additional data in the request:

Parameters:

Name Type Description Default
method str

The HTTP method to use.

required
path str

The path to send the request to, relative to BASE_URL.

required
payload Optional[Payload]

The payload to send (JSON).

None
data Optional[Parameters]

The data to send.

None
parameters Optional[Parameters]

The parameters to use.

None
headers Optional[Headers]

The headers to use.

None

Returns:

Type Description
str

The response string.

Raises:

Type Description
ClientError

All request attempts failed.

Source code in aoc/http.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
async def request(
    self,
    method: str,
    path: str,
    payload: Optional[Payload] = None,
    data: Optional[Parameters] = None,
    parameters: Optional[Parameters] = None,
    headers: Optional[Headers] = None,
) -> str:
    """Sends requests to the Advent of Code servers.

    This method sends additional data in the request:

    - `cookies`: The [`token`][aoc.http.HTTPClient.token] in the
      [`TOKEN_COOKIE_NAME`][aoc.constants.TOKEN_COOKIE_NAME] cookie.

    - `headers`: The [`HEADERS`][aoc.http.HEADERS].

    Arguments:
        method: The HTTP method to use.
        path: The path to send the request to, relative to [`BASE_URL`][aoc.constants.BASE_URL].
        payload: The payload to send (JSON).
        data: The data to send.
        parameters: The parameters to use.
        headers: The headers to use.

    Returns:
        The response string.

    Raises:
        ClientError: All request attempts failed.
    """
    attempts = self.retries + 1

    error: Optional[ClientError] = None

    session_cookies = {TOKEN_COOKIE_NAME: self.token}
    session_headers = HEADERS
    base_url = BASE_URL

    while attempts:
        try:
            async with ClientSession(
                base_url=base_url, cookies=session_cookies, headers=session_headers
            ) as session:
                response = await session.request(
                    method,
                    path,
                    params=parameters,
                    data=data,
                    json=payload,
                    headers=headers,
                )

                response.raise_for_status()

        except ClientError as origin:
            error = origin

        else:
            return await response.text()

        attempts -= 1

    if error:
        raise error

    return EMPTY  # pragma: never

request_route(route: Route, payload: Optional[Payload] = None, data: Optional[Parameters] = None, parameters: Optional[Parameters] = None, headers: Optional[Headers] = None) -> str async

Sends requests to the Advent of Code servers using routes.

response = await client.request_route(
    route,
    payload=payload,
    data=data,
    parameters=parameters,
    headers=headers,
)

is equivalent to:

response = await client.request(
    route.method,
    route.path,
    payload=payload,
    data=data,
    parameters=parameters,
    headers=headers,
)

See request for more information.

Parameters:

Name Type Description Default
route Route

The route to send the request to.

required
payload Optional[Payload]

The payload to send (JSON).

None
data Optional[Parameters]

The data to send.

None
parameters Optional[Parameters]

The parameters to use.

None
headers Optional[Headers]

The headers to use.

None

Returns:

Type Description
str

The response string.

Raises:

Type Description
ClientError

All request attempts failed.

Source code in aoc/http.py
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
async def request_route(
    self,
    route: Route,
    payload: Optional[Payload] = None,
    data: Optional[Parameters] = None,
    parameters: Optional[Parameters] = None,
    headers: Optional[Headers] = None,
) -> str:
    """Sends requests to the Advent of Code servers using routes.

    ```python
    response = await client.request_route(
        route,
        payload=payload,
        data=data,
        parameters=parameters,
        headers=headers,
    )
    ```

    is equivalent to:

    ```python
    response = await client.request(
        route.method,
        route.path,
        payload=payload,
        data=data,
        parameters=parameters,
        headers=headers,
    )
    ```

    See [`request`][aoc.http.HTTPClient.request] for more information.

    Arguments:
        route: The route to send the request to.
        payload: The payload to send (JSON).
        data: The data to send.
        parameters: The parameters to use.
        headers: The headers to use.

    Returns:
        The response string.

    Raises:
        ClientError: All request attempts failed.
    """
    return await self.request(
        route.method,
        route.path,
        payload=payload,
        data=data,
        parameters=parameters,
        headers=headers,
    )

download_data(key: Key) -> str async

Downloads the data for the problem for the given key.

Parameters:

Name Type Description Default
key Key

The key to download the data for.

required

Returns:

Type Description
str

The problem data downloaded.

Raises:

Type Description
ClientError

All request attempts failed.

Source code in aoc/http.py
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
async def download_data(self, key: Key) -> str:
    """Downloads the data for the problem for the given `key`.

    Arguments:
        key: The key to download the data for.

    Returns:
        The problem data downloaded.

    Raises:
        ClientError: All request attempts failed.
    """
    route = Route.with_parameters(
        GET, "/{year}/day/{day}/input", year=key.year.value, day=key.day.value
    )

    return await self.request_route(route)

submit_answer(key: Key, part: Part, answer: Any) -> State async

Submits the answer for the problem part and the given key.

Parameters:

Name Type Description Default
key Key

The key of the problem to submit the answer for.

required
part Part

The part of the problem to submit the answer for.

required
answer Any

The answer to submit.

required

Returns:

Type Description
State

The state fetched from the response.

Raises:

Type Description
ClientError

All request attempts failed.

Source code in aoc/http.py
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
async def submit_answer(self, key: Key, part: Part, answer: Any) -> State:
    """Submits the `answer` for the problem `part` and the given `key`.

    Arguments:
        key: The key of the problem to submit the answer for.
        part: The part of the problem to submit the answer for.
        answer: The answer to submit.

    Returns:
        The state fetched from the response.

    Raises:
        ClientError: All request attempts failed.
    """
    route = Route.with_parameters(
        POST, "/{year}/day/{day}/answer", year=key.year.value, day=key.day.value
    )

    data = {PART: part.value, ANSWER: answer}

    response = await self.request_route(route, data=data)

    return State.match(response)