Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 32 additions & 12 deletions docs/configuration.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ <h1>Configuration</h1>
base_url="https://www.ratemyprofessors.com/graphql",
timeout_seconds=10.0,
max_retries=3,
rate_limit_per_minute=60,
)
with RMPClient(config) as client:
...</code></pre>
Expand Down Expand Up @@ -104,12 +103,6 @@ <h2 id="available-options">
<td><code>3</code></td>
<td>Number of retry attempts for failed requests</td>
</tr>
<tr>
<td><code>rate_limit_per_minute</code></td>
<td><code>int</code></td>
<td><code>60</code></td>
<td>Max requests per minute (token bucket)</td>
</tr>
<tr>
<td><code>user_agent</code></td>
<td><code>str</code></td>
Expand All @@ -128,19 +121,46 @@ <h2 id="rate-limiting">
Rate Limiting <a href="#rate-limiting" class="anchor">#</a>
</h2>
<p>
The client uses a token-bucket algorithm. Tokens replenish continuously.
Each request consumes one token. If no tokens are available, the request
blocks until one becomes available.
The client uses a token-bucket algorithm fixed at <strong>60 requests per
minute</strong>. Tokens replenish continuously at 1 per second. Each
request consumes one token; if none are available the request blocks
until one becomes available. This limit is not configurable.
</p>
<pre><code class="language-python">config = RMPClientConfig(rate_limit_per_minute=30)</code></pre>

<h2 id="retries">Retries <a href="#retries" class="anchor">#</a></h2>
<p>
On 5xx errors or network failures, the client retries up to
<code>max_retries</code> times. 4xx errors are
<strong>not</strong> retried. After exhausting retries, a
<code>RetryError</code> is raised.
<code>RetryError</code> is raised. Each retry attempt consumes a token
from the rate limiter before firing.
</p>
<table>
<tr>
<th>Situation</th>
<th>Retried?</th>
</tr>
<tr>
<td>Network error (timeout, connection reset)</td>
<td>Yes</td>
</tr>
<tr>
<td>5xx server error</td>
<td>Yes</td>
</tr>
<tr>
<td>4xx client error (400, 401, 403, 404)</td>
<td>No &mdash; raises <code>HttpError</code> immediately</td>
</tr>
<tr>
<td>429 Too Many Requests</td>
<td>Yes &mdash; exponential back-off, up to <code>max_retries</code> times</td>
</tr>
<tr>
<td>GraphQL <code>errors</code> in a 200 response</td>
<td>No &mdash; raises <code>RMPAPIError</code> immediately</td>
</tr>
</table>
<pre><code class="language-python">config = RMPClientConfig(max_retries=5)</code></pre>

<h2 id="timeouts">Timeouts <a href="#timeouts" class="anchor">#</a></h2>
Expand Down
212 changes: 137 additions & 75 deletions docs/index.html
Original file line number Diff line number Diff line change
@@ -1,75 +1,120 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>RateMyProfessors API Client (Python)</title>
<link rel="icon" href="favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="style.css" />
<link id="hljs-theme" rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/styles/github-dark.min.css" />
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/highlight.min.js"></script>
<script>
(function(){var t=localStorage.getItem('rmp-docs-theme');var d=t==='dark'||(!t&&window.matchMedia('(prefers-color-scheme:dark)').matches);if(d)document.documentElement.classList.add('dark')})();
</script>
</head>
<body>
<header class="topbar">
<div class="topbar-right">
<a href="https://github.com/amaanjaved1/Rate-My-Professors-API-Client" class="topbar-btn" target="_blank" rel="noopener" aria-label="GitHub">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z"/></svg>
</a>
<button class="topbar-btn" id="theme-toggle" aria-label="Toggle theme"></button>
</div>
</header>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>RateMyProfessors API Client (Python)</title>
<link rel="icon" href="favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="style.css" />
<link
id="hljs-theme"
rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/styles/github-dark.min.css"
/>
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11/build/highlight.min.js"></script>
<script>
(function () {
var t = localStorage.getItem("rmp-docs-theme");
var d =
t === "dark" ||
(!t && window.matchMedia("(prefers-color-scheme:dark)").matches);
if (d) document.documentElement.classList.add("dark");
})();
</script>
</head>
<body>
<header class="topbar">
<div class="topbar-right">
<a
href="https://github.com/amaanjaved1/Rate-My-Professors-API-Client"
class="topbar-btn"
target="_blank"
rel="noopener"
aria-label="GitHub"
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<path
d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z"
/>
</svg>
</a>
<button
class="topbar-btn"
id="theme-toggle"
aria-label="Toggle theme"
></button>
</div>
</header>

<aside class="sidebar">
<div class="sidebar-brand">RMP API Docs</div>
<nav><ul>
<li><a href="index.html" class="active">Home</a></li>
<li><a href="usage.html">Usage</a></li>
<li><a href="reference.html">API Reference</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="extras.html">Extras</a></li>
</ul></nav>
</aside>
<aside class="sidebar">
<div class="sidebar-brand">RMP API Docs</div>
<nav>
<ul>
<li><a href="index.html" class="active">Home</a></li>
<li><a href="usage.html">Usage</a></li>
<li><a href="reference.html">API Reference</a></li>
<li><a href="configuration.html">Configuration</a></li>
<li><a href="extras.html">Extras</a></li>
</ul>
</nav>
</aside>

<main class="content">
<h1>RateMyProfessors API Client</h1>
<div class="badges">
<a href="https://pypi.org/project/ratemyprofessors-client/"><img src="https://img.shields.io/pypi/v/ratemyprofessors-client?color=10b981&cacheSeconds=300" alt="PyPI" /></a>
<a href="https://pypi.org/project/ratemyprofessors-client/"><img src="https://img.shields.io/pepy/dt/ratemyprofessors-client?cacheSeconds=300" alt="downloads" /></a>
</div>
<p>
An unofficial, typed Python client for <a href="https://www.ratemyprofessors.com">RateMyProfessors</a>.
All data is fetched via RMP's GraphQL API &mdash; no HTML scraping or browser automation required.
</p>
<main class="content">
<h1>RateMyProfessors API Client</h1>
<div class="badges">
<a href="https://pypi.org/project/ratemyprofessors-client/"
><img
src="https://img.shields.io/pypi/v/ratemyprofessors-client?color=10b981&cacheSeconds=300"
alt="PyPI"
/></a>
<a href="https://pypi.org/project/ratemyprofessors-client/"
><img
src="https://img.shields.io/pepy/dt/ratemyprofessors-client?cacheSeconds=300"
alt="downloads"
/></a>
</div>
<p>
An unofficial, typed Python client for
<a href="https://www.ratemyprofessors.com">RateMyProfessors</a>. All
data is fetched via RMP's GraphQL API &mdash; no HTML scraping or
browser automation required.
</p>

<div class="callout">
<strong>Disclaimer:</strong> This library is unofficial and may break if RMP changes their internal API. Use responsibly and respect rate limits.
</div>
<div class="callout">
<strong>Disclaimer:</strong> This library is unofficial and may break if
RMP changes their internal API. Use responsibly and respect rate limits.
</div>

<h2 id="features">Features <a href="#features" class="anchor">#</a></h2>
<ul>
<li>Strong typing via Pydantic models</li>
<li>Automatic retries with configurable max attempts</li>
<li>Token-bucket rate limiting (default 60 req/min)</li>
<li>In-memory caching for ratings pages</li>
<li>Cursor-based pagination for all list/search endpoints</li>
<li>Clear error hierarchy for precise exception handling</li>
<li>Built-in helpers for ingestion workflows (sentiment, comment validation, course codes)</li>
</ul>
<h2 id="features">Features <a href="#features" class="anchor">#</a></h2>
<ul>
<li>Strong typing via Pydantic models</li>
<li>Automatic retries with configurable max attempts</li>
<li>Token-bucket rate limiting (fixed at 60 req/min)</li>
<li>Cursor-based pagination for all list/search endpoints</li>
<li>Clear error hierarchy for precise exception handling</li>
<li>
Built-in helpers for ingestion workflows (sentiment, comment
validation, course codes)
</li>
</ul>

<h2 id="requirements">Requirements <a href="#requirements" class="anchor">#</a></h2>
<ul>
<li><strong>Python 3.10</strong> or later</li>
<li>Works with type checkers (Pydantic models, fully typed API)</li>
</ul>
<h2 id="requirements">
Requirements <a href="#requirements" class="anchor">#</a>
</h2>
<ul>
<li><strong>Python 3.10</strong> or later</li>
<li>Works with type checkers (Pydantic models, fully typed API)</li>
</ul>

<h2 id="installation">Installation <a href="#installation" class="anchor">#</a></h2>
<pre><code class="language-bash">pip install ratemyprofessors-client</code></pre>
<h2 id="installation">
Installation <a href="#installation" class="anchor">#</a>
</h2>
<pre><code class="language-bash">pip install ratemyprofessors-client</code></pre>

<h2 id="quick-start">Quick Start <a href="#quick-start" class="anchor">#</a></h2>
<pre><code class="language-python">from rmp_client import RMPClient
<h2 id="quick-start">
Quick Start <a href="#quick-start" class="anchor">#</a>
</h2>
<pre><code class="language-python">from rmp_client import RMPClient

with RMPClient() as client:
prof = client.get_professor("2823076")
Expand All @@ -78,16 +123,33 @@ <h2 id="quick-start">Quick Start <a href="#quick-start" class="anchor">#</a></h2
for rating in client.iter_professor_ratings(prof.id):
print(rating.date, rating.quality, rating.comment)</code></pre>

<h2 id="documentation">Documentation <a href="#documentation" class="anchor">#</a></h2>
<ul>
<li><a href="usage.html">Usage</a> &mdash; Quickstart examples for every endpoint</li>
<li><a href="configuration.html">Configuration</a> &mdash; Tuning retries, rate limits, timeouts, and headers</li>
<li><a href="reference.html">API Reference</a> &mdash; Full method and type reference</li>
<li><a href="extras.html">Extras</a> &mdash; Ingestion helpers (sentiment, comment validation, course mapping)</li>
</ul>
</main>
<h2 id="documentation">
Documentation <a href="#documentation" class="anchor">#</a>
</h2>
<ul>
<li>
<a href="usage.html">Usage</a> &mdash; Quickstart examples for every
endpoint
</li>
<li>
<a href="configuration.html">Configuration</a> &mdash; Tuning retries,
timeouts, and headers
</li>
<li>
<a href="reference.html">API Reference</a> &mdash; Full method and
type reference
</li>
<li>
<a href="extras.html">Extras</a> &mdash; Ingestion helpers (sentiment,
comment validation, course mapping)
</li>
</ul>
</main>

<aside class="toc"><div class="toc-title">On this page</div><ul id="toc-list"></ul></aside>
<script src="script.js"></script>
</body>
<aside class="toc">
<div class="toc-title">On this page</div>
<ul id="toc-list"></ul>
</aside>
<script src="script.js"></script>
</body>
</html>
9 changes: 4 additions & 5 deletions docs/reference.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ <h2 id="school-methods">School Methods <a href="#school-methods" class="anchor">
<tr><td><code>search_schools(query, *, page_size=20, cursor=None)</code></td><td><code>SchoolSearchResult</code></td><td>Search schools by name</td></tr>
<tr><td><code>get_school(school_id)</code></td><td><code>School</code></td><td>Fetch a single school with category ratings</td></tr>
<tr><td><code>get_compare_schools(school_id_1, school_id_2)</code></td><td><code>CompareSchoolsResult</code></td><td>Fetch two schools side by side</td></tr>
<tr><td><code>get_school_ratings_page(school_id, *, cursor=None, page_size=20)</code></td><td><code>SchoolRatingsPage</code></td><td>Get one page of school ratings (cached)</td></tr>
<tr><td><code>get_school_ratings_page(school_id, *, cursor=None, page_size=20)</code></td><td><code>SchoolRatingsPage</code></td><td>Get one page of school ratings</td></tr>
<tr><td><code>iter_school_ratings(school_id, *, page_size=20, since=None)</code></td><td><code>Iterator[SchoolRating]</code></td><td>Iterate all school ratings</td></tr>
</table>

Expand All @@ -58,15 +58,15 @@ <h2 id="professor-methods">Professor Methods <a href="#professor-methods" class=
<tr><td><code>list_professors_for_school(school_id, *, query=None, page_size=20, cursor=None)</code></td><td><code>ProfessorSearchResult</code></td><td>List professors at a school</td></tr>
<tr><td><code>iter_professors_for_school(school_id, *, query=None, page_size=20)</code></td><td><code>Iterator[Professor]</code></td><td>Iterate all professors at a school</td></tr>
<tr><td><code>get_professor(professor_id)</code></td><td><code>Professor</code></td><td>Fetch a single professor</td></tr>
<tr><td><code>get_professor_ratings_page(professor_id, *, cursor=None, page_size=20, course_filter=None)</code></td><td><code>ProfessorRatingsPage</code></td><td>Get one page of professor ratings (cached)</td></tr>
<tr><td><code>get_professor_ratings_page(professor_id, *, cursor=None, page_size=20, course_filter=None)</code></td><td><code>ProfessorRatingsPage</code></td><td>Get one page of professor ratings</td></tr>
<tr><td><code>iter_professor_ratings(professor_id, *, page_size=20, since=None, course_filter=None)</code></td><td><code>Iterator[Rating]</code></td><td>Iterate all professor ratings</td></tr>
</table>

<h2 id="low-level">Low-level <a href="#low-level" class="anchor">#</a></h2>
<table>
<tr><th>Method</th><th>Returns</th><th>Description</th></tr>
<tr><td><code>raw_query(payload)</code></td><td><code>dict</code></td><td>Send a raw GraphQL payload</td></tr>
<tr><td><code>close()</code></td><td><code>None</code></td><td>Close the HTTP client and clear caches</td></tr>
<tr><td><code>close()</code></td><td><code>None</code></td><td>Close the HTTP client</td></tr>
</table>

<hr />
Expand All @@ -78,7 +78,7 @@ <h3 id="School">School</h3>
<p><code>id</code>, <code>name</code>, <code>location</code>, <code>overall_quality</code>, <code>num_ratings</code>, <code>reputation</code>, <code>safety</code>, <code>happiness</code>, <code>facilities</code>, <code>social</code>, <code>location_rating</code>, <code>clubs</code>, <code>opportunities</code>, <code>internet</code>, <code>food</code></p>

<h3 id="Professor">Professor</h3>
<p><code>id</code>, <code>name</code>, <code>department</code>, <code>school</code> (School), <code>url</code>, <code>overall_rating</code>, <code>num_ratings</code>, <code>percent_take_again</code>, <code>level_of_difficulty</code>, <code>tags</code>, <code>rating_distribution</code></p>
<p><code>id</code>, <code>name</code>, <code>department</code>, <code>school</code> (School), <code>overall_rating</code>, <code>num_ratings</code>, <code>percent_take_again</code>, <code>level_of_difficulty</code>, <code>tags</code>, <code>rating_distribution</code></p>

<h3 id="Rating">Rating</h3>
<p><code>date</code>, <code>comment</code>, <code>quality</code>, <code>difficulty</code>, <code>tags</code>, <code>course_raw</code>, <code>details</code>, <code>thumbs_up</code>, <code>thumbs_down</code></p>
Expand All @@ -103,7 +103,6 @@ <h2 id="errors">Errors <a href="#errors" class="anchor">#</a></h2>
<tr><th>Error</th><th>Description</th></tr>
<tr><td><code>HttpError</code></td><td>Non-2xx HTTP response. Has <code>status_code</code>, <code>url</code>, <code>body</code>.</td></tr>
<tr><td><code>ParsingError</code></td><td>Could not parse the GraphQL response.</td></tr>
<tr><td><code>RateLimitError</code></td><td>Local rate limiter blocked the request.</td></tr>
<tr><td><code>RetryError</code></td><td>All retry attempts exhausted.</td></tr>
<tr><td><code>RMPAPIError</code></td><td>GraphQL API returned an <code>errors</code> array.</td></tr>
<tr><td><code>ConfigurationError</code></td><td>Invalid client configuration.</td></tr>
Expand Down
4 changes: 2 additions & 2 deletions docs/usage.html
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ <h2 id="get-professor">Get a Professor by ID <a href="#get-professor" class="anc
print(f"Difficulty: {prof.level_of_difficulty}")
print(f"Would take again: {prof.percent_take_again}%")</code></pre>

<h2 id="professor-ratings">Professor Ratings (Paginated, Cached) <a href="#professor-ratings" class="anchor">#</a></h2>
<h2 id="professor-ratings">Professor Ratings (Paginated) <a href="#professor-ratings" class="anchor">#</a></h2>
<pre><code class="language-python">with RMPClient() as client:
page = client.get_professor_ratings_page("2823076", page_size=10)
print(f"Professor: {page.professor.name}")
Expand All @@ -103,7 +103,7 @@ <h2 id="iterate-professor-ratings">Iterate All Professor Ratings <a href="#itera
for rating in client.iter_professor_ratings("2823076", since=date(2024, 1, 1)):
print(rating.date, rating.quality, rating.comment)</code></pre>

<h2 id="school-ratings">School Ratings (Paginated, Cached) <a href="#school-ratings" class="anchor">#</a></h2>
<h2 id="school-ratings">School Ratings (Paginated) <a href="#school-ratings" class="anchor">#</a></h2>
<pre><code class="language-python">with RMPClient() as client:
page = client.get_school_ratings_page("1466", page_size=10)
for rating in page.ratings:
Expand Down
Loading
Loading