Skip to content

Commit 81adf7f

Browse files
committed
Initial commit
1 parent 0e7cbc1 commit 81adf7f

26 files changed

Lines changed: 1293 additions & 1 deletion
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# See: https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/
2+
# PyPI Trusted Publishing: https://docs.pypi.org/trusted-publishers/
3+
name: Publish Python distribution to PyPI and TestPyPI
4+
5+
on: push
6+
7+
jobs:
8+
build:
9+
name: Build distribution
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- uses: actions/checkout@v4
14+
with:
15+
persist-credentials: false
16+
17+
- name: Set up Python
18+
uses: actions/setup-python@v5
19+
with:
20+
python-version: "3.12"
21+
22+
- name: Install dependencies
23+
run: |
24+
python -m pip install --upgrade pip
25+
pip install build pytest
26+
pip install -e ".[dev]"
27+
28+
- name: Run tests
29+
run: pytest tests/ -v
30+
31+
- name: Install pypa/build
32+
run: pip install build
33+
34+
- name: Build a binary wheel and a source tarball
35+
run: python -m build
36+
37+
- name: Store the distribution packages
38+
uses: actions/upload-artifact@v4
39+
with:
40+
name: python-package-distributions
41+
path: dist/
42+
43+
publish-to-pypi:
44+
name: Publish to PyPI
45+
if: startsWith(github.ref, 'refs/tags/')
46+
needs: [build]
47+
runs-on: ubuntu-latest
48+
environment:
49+
name: pypi
50+
url: https://pypi.org/project/ratemyprofessors-client/
51+
permissions:
52+
id-token: write
53+
54+
steps:
55+
- name: Download all the dists
56+
uses: actions/download-artifact@v4
57+
with:
58+
name: python-package-distributions
59+
path: dist/
60+
- name: Publish distribution to PyPI
61+
uses: pypa/gh-action-pypi-publish@release/v1
62+
63+
publish-to-testpypi:
64+
name: Publish to TestPyPI
65+
needs: [build]
66+
runs-on: ubuntu-latest
67+
environment:
68+
name: testpypi
69+
url: https://test.pypi.org/project/ratemyprofessors-client/
70+
permissions:
71+
id-token: write
72+
73+
steps:
74+
- name: Download all the dists
75+
uses: actions/download-artifact@v4
76+
with:
77+
name: python-package-distributions
78+
path: dist/
79+
- name: Publish distribution to TestPyPI
80+
uses: pypa/gh-action-pypi-publish@release/v1
81+
with:
82+
repository-url: https://test.pypi.org/legacy/

.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Build artifacts (see: https://packaging.python.org/en/latest/overview/)
2+
dist/
3+
build/
4+
*.egg-info/
5+
*.egg
6+
7+
# Virtual environments
8+
.venv/
9+
venv/
10+
env/
11+
12+
# IDE / OS
13+
.idea/
14+
.vscode/
15+
*.swp
16+
.DS_Store
17+
18+
# Testing / coverage
19+
.pytest_cache/
20+
.coverage
21+
htmlcov/
22+
23+
# mypy
24+
.mypy_cache/

LICENSE

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Amaan
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
22+

README.md

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,70 @@
1-
# Rate-My-Professors-API-Client
1+
# RateMyProfessors API Client
2+
3+
Typed, retrying, rate-limited unofficial client for RateMyProfessors, with optional
4+
helpers for ingestion workflows (sentiment, dedupe, course-code normalization).
5+
6+
> Note: This library is **unofficial** and may break if RMP changes their internal API.
7+
8+
## Installation
9+
10+
```bash
11+
pip install ratemyprofessors-client
12+
```
13+
14+
With optional sentiment extras (TextBlob):
15+
16+
```bash
17+
pip install 'ratemyprofessors-client[sentiment]'
18+
```
19+
20+
## Quickstart
21+
22+
```python
23+
from rmp_client import RMPClient
24+
25+
SCHOOL_ID = 1466 # example: Queen's University ID on RMP
26+
27+
with RMPClient() as client:
28+
for prof in client.iter_professors_for_school(SCHOOL_ID, page_size=20):
29+
print(prof.name, prof.overall_rating, prof.num_ratings)
30+
```
31+
32+
Fetch details and iterate ratings incrementally:
33+
34+
```python
35+
from datetime import date
36+
from rmp_client import RMPClient
37+
38+
with RMPClient() as client:
39+
professor = client.get_professor("PROFESSOR_ID")
40+
41+
for rating in client.iter_professor_ratings(professor.id, since=date(2024, 1, 1)):
42+
print(rating.date, rating.quality, rating.comment)
43+
```
44+
45+
## Extras
46+
47+
Optional helpers live under `rmp_client.extras`:
48+
49+
- `rmp_client.extras.sentiment.analyze_sentiment`
50+
- `rmp_client.extras.dedupe.normalize_comment` / `is_valid_comment`
51+
- `rmp_client.extras.course_codes.build_course_mapping`
52+
53+
See `docs/` and `examples/` for more. This repo also includes an
54+
`examples/ingest_supabase.py` script that mirrors a Supabase-centric
55+
scraping pipeline using this client.
56+
57+
## Publishing to PyPI
58+
59+
This project follows the [Python Packaging User Guide](https://packaging.python.org/en/latest/overview/) and uses [PyPI Trusted Publishing](https://docs.pypi.org/trusted-publishers/) with GitHub Actions.
60+
61+
1. **One-time setup**: On [pypi.org](https://pypi.org/manage/account/publishing/) add a trusted publisher for this repo (workflow `publish-to-pypi.yml`, environment `pypi`). Create a `pypi` environment in the repo and enable “Required reviewers” for production releases.
62+
2. **Release**: Create and push a tag (e.g. `v0.1.0`). The workflow builds both a [wheel and an sdist](https://packaging.python.org/en/latest/overview/#python-binary-distributions) and publishes to PyPI. Any push builds and publishes to TestPyPI (use the `testpypi` environment).
63+
64+
Local build (no publish):
65+
66+
```bash
67+
pip install build
68+
python -m build
69+
# Outputs in dist/: .whl (wheel) and .tar.gz (sdist)
70+
```

docs/configuration.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
### Configuration
2+
3+
The client is configured via `RMPClientConfig` and environment variables.
4+
5+
```python
6+
from rmp_client import RMPClientConfig, RMPClient
7+
8+
config = RMPClientConfig(
9+
base_url="https://www.ratemyprofessors.com/graphql",
10+
timeout_seconds=10.0,
11+
max_retries=3,
12+
rate_limit_per_minute=60,
13+
)
14+
15+
client = RMPClient(config)
16+
```
17+
18+
Environment variables (optional):
19+
20+
- `RMP_CLIENT_BASE_URL`
21+
- `RMP_CLIENT_TIMEOUT_SECONDS`
22+
- `RMP_CLIENT_MAX_RETRIES`
23+
- `RMP_CLIENT_RATE_LIMIT_PER_MINUTE`
24+

docs/extras.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
### Extras for Ingestion Pipelines
2+
3+
These helpers are optional and live under `rmp_client.extras`.
4+
5+
Install with:
6+
7+
```bash
8+
pip install 'ratemyprofessors-client[sentiment]'
9+
```
10+
11+
#### Sentiment
12+
13+
```python
14+
from rmp_client.extras.sentiment import analyze_sentiment
15+
16+
result = analyze_sentiment("Great prof, explains concepts clearly.")
17+
print(result.score, result.label)
18+
```
19+
20+
#### Dedupe helpers
21+
22+
```python
23+
from rmp_client.extras.dedupe import normalize_comment, is_valid_comment
24+
25+
raw = " This prof is AMAZING!!! "
26+
normalized = normalize_comment(raw)
27+
if is_valid_comment(normalized):
28+
...
29+
```
30+
31+
#### Course code helpers
32+
33+
```python
34+
from rmp_client.extras.course_codes import build_course_mapping
35+
36+
scraped = ["ANAT 215 (12)", "phys115"]
37+
valid = ["ANAT 215", "PHYS 115"]
38+
39+
mapping = build_course_mapping(scraped, valid)
40+
```
41+

docs/index.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
### RateMyProfessors API Client
2+
3+
This is an unofficial, typed client for RateMyProfessors.
4+
5+
- Strong typing via Pydantic models
6+
- Automatic retries and simple rate limiting
7+
- Clear error hierarchy
8+
- Optional helpers for ingestion workflows (sentiment, dedupe, course codes)
9+
10+
See:
11+
12+
- `usage.md` for quickstart examples
13+
- `configuration.md` for tuning retries, rate limits, and headers
14+
- `extras.md` for ingestion helpers
15+
- `reference.md` for generated API reference (once wired)
16+

docs/reference.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
### API Reference
2+
3+
This file is a placeholder for generated API reference using a tool such as `mkdocstrings`
4+
or Sphinx autodoc.
5+
6+
Key entry points:
7+
8+
- `rmp_client.RMPClient`
9+
- `rmp_client.RMPClientConfig`
10+
- `rmp_client.errors`
11+
- `rmp_client.models`
12+

docs/usage.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
### Usage
2+
3+
#### Basic client
4+
5+
```python
6+
from rmp_client import RMPClient
7+
8+
with RMPClient() as client:
9+
result = client.search_professors("Smith", page_size=10)
10+
for prof in result.professors:
11+
print(prof.name, prof.overall_rating)
12+
```
13+
14+
#### Iterate professors for a school
15+
16+
```python
17+
from rmp_client import RMPClient
18+
19+
SCHOOL_ID = 1466 # Queen's University at Kingston, for example
20+
21+
with RMPClient() as client:
22+
for prof in client.iter_professors_for_school(SCHOOL_ID, page_size=50):
23+
print(prof.name, prof.num_ratings)
24+
```
25+
26+
#### Fetch professor details and ratings
27+
28+
```python
29+
from datetime import date
30+
from rmp_client import RMPClient
31+
32+
with RMPClient() as client:
33+
professor = client.get_professor("PROFESSOR_ID")
34+
35+
for rating in client.iter_professor_ratings(professor.id, since=date(2024, 1, 1)):
36+
print(rating.date, rating.quality, rating.comment)
37+
```
38+

examples/get_professor.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from rmp_client import RMPClient
2+
3+
4+
def main() -> None:
5+
professor_id = "PROFESSOR_ID"
6+
7+
with RMPClient() as client:
8+
professor = client.get_professor(professor_id)
9+
print(professor)
10+
11+
12+
if __name__ == "__main__":
13+
main()
14+

0 commit comments

Comments
 (0)