Metadata-Version: 2.4
Name: pywhosampled
Version: 1.0.0
Summary: A Python library for fetching song sample, cover, and remix information from WhoSampled.com
Home-page: https://github.com/amanhasfallenintoriverincity/pywhosampled
Author: amanhasfallenintoriverincity
Keywords: whosampled samples music covers remixes scraper api
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: cloudscraper>=1.2.71
Requires-Dist: beautifulsoup4>=4.6.0
Dynamic: author
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

<div align="center">

# whosampled

[![PyPI version](https://img.shields.io/pypi/v/pywhosampled?color=blue)](https://pypi.org/project/pywhosampled/)
[![Python](https://img.shields.io/pypi/pyversions/pywhosampled?color=blue)](https://pypi.org/project/pywhosampled/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
[![Status](https://img.shields.io/pypi/status/pywhosampled)](https://pypi.org/project/pywhosampled/)
[![Downloads](https://img.shields.io/pypi/dm/pywhosampled?color=blue)](https://pypi.org/project/pywhosampled/)

A Python library for fetching song sample, cover, and remix data from [WhoSampled.com](https://www.whosampled.com/).

**[English](README.md)** · [한국어](README.ko.md)

</div>

---

## Table of Contents

- [Installation](#installation)
- [Quick Start](#quick-start)
- [Features](#features)
- [API Reference](#api-reference)
- [Data Models](#data-models)
- [Exceptions](#exceptions)
- [Examples](#examples)
- [Rate Limiting](#rate-limiting)
- [Disclaimer](#disclaimer)
- [License](#license)

## Installation

```bash
pip install pywhosampled
```

From source:

```bash
git clone https://github.com/amanhasfallenintoriverincity/pywhosampled.git
cd whosampled
pip install .
```

Requires Python 3.8+. The two runtime dependencies are `cloudscraper` (handles Cloudflare anti-bot challenges on WhoSampled) and `beautifulsoup4` (HTML parsing).

## Quick Start

```python
from whosampled import WhoSampledClient

client = WhoSampledClient()

# Search for a track
results = client.search("Kanye West Bound 2")
print(results.top_hit)  # Track: Bound 2 by Kanye West feat. Charlie Wilson

# Get track details (includes up to 3-5 samples/covers/remixes)
detail = client.get_track_detail("Kanye West", "Bound 2")

print(detail.track.full_title)    # Bound 2 by Kanye West feat. Charlie Wilson
print(detail.track.year)          # 2013
print(detail.track.album)         # Yeezus
print(detail.track.genre)         # Hip-Hop / Rap / R&B
print(detail.track.producers)     # ['Kanye West', "Ché Pope", 'Eric Danchick', ...]

# What this song samples
for s in detail.samples:
    print(f"  {s.track_title} by {s.artist} ({s.year}) [{s.sample_type}]")
#   Bound by Ponderosa Twins Plus One (1971) [Multiple Elements]
#   Sweet Nothin's by Brenda Lee (1959) [Vocals / Lyrics]
#   Aeroplane (Reprise) by Wee (1977) [Multiple Elements]

# What sampled this song
for s in detail.sampled_in:
    print(f"  {s.track_title} by {s.artist} ({s.year})")

# Cover versions
for s in detail.covers:
    print(f"  {s.track_title} by {s.artist} ({s.year})")
```

## Features

- Search tracks, artists, and sample connections by keyword
- Pull track metadata: album, year, genre, label, producers, tags
- List what a song samples, what sampled it, covers, and remixes
- Fetch complete sample/cover/remix lists via the "See All" pages
- Get artist stats, aliases, group memberships, and top tracks
- Built-in rate limiting with configurable delay between requests
- Automatic retries for transient failures (Cloudflare 403s, 429s, 5xx, network errors)
- Context manager support for clean session handling

## API Reference

### `WhoSampledClient`

```python
client = WhoSampledClient(
    delay=1.0,        # minimum seconds between requests
    timeout=30,       # request timeout in seconds
    user_agent=None,  # custom User-Agent string
    max_retries=3,    # retry count for failed requests
)
```

<details>
<summary><b>search(query: str) → SearchResult</b></summary>

Search WhoSampled for tracks, artists, or connections.

```python
results = client.search("Eminem My Name Is")
results.top_hit       # Track — best match
results.tracks        # List[Track] — other track matches
results.connections   # List[str] — sample connection descriptions
```
</details>

<details>
<summary><b>search_tracks(query: str) → List[Track]</b></summary>

Search and return only tracks, with the top hit first. Deduplicates by URL.

```python
tracks = client.search_tracks("Madonna Vogue")
for track in tracks:
    print(track.full_title)
```
</details>

<details>
<summary><b>get_track_detail(artist: str, track: str) → TrackDetail</b></summary>

Get full track details including samples, covers, and remixes. The main track page includes up to 3-5 items per category. For complete lists, use `get_all_samples` / `get_all_sampled_in` / `get_all_covers` / `get_all_remixes`.

```python
detail = client.get_track_detail("Kanye West", "Bound 2")
detail.track        # Track — metadata
detail.samples      # List[Sample] — tracks this song samples
detail.sampled_in   # List[Sample] — tracks that sample this song
detail.covers       # List[Sample] — cover versions
detail.remixes      # List[Sample] — remixes
```
</details>

<details>
<summary><b>get_track_detail_by_url(url: str) → TrackDetail</b></summary>

Get track details from a full or relative WhoSampled track URL.

```python
detail = client.get_track_detail_by_url("/Kanye-West/Bound-2/")
# or: client.get_track_detail_by_url("https://www.whosampled.com/Kanye-West/Bound-2/")
```
</details>

<details>
<summary><b>get_all_samples(artist: str, track: str) → List[Sample]</b></summary>

Get the complete list of tracks this song samples, via the "See All" page.
</details>

<details>
<summary><b>get_all_sampled_in(artist: str, track: str) → List[Sample]</b></summary>

Get the complete list of tracks that sample this song.
</details>

<details>
<summary><b>get_all_covers(artist: str, track: str) → List[Sample]</b></summary>

Get cover versions. WhoSampled does not expose a dedicated "See All" page for covers, so this returns the covers listed on the main track detail page.
</details>

<details>
<summary><b>get_all_remixes(artist: str, track: str) → List[Sample]</b></summary>

Get remixes. Same as covers: WhoSampled has no "See All" page for remixes, so this returns the remixes from the main track detail page.
</details>

<details>
<summary><b>get_artist(artist: str) → ArtistDetail</b></summary>

Get artist page details including stats, aliases, groups, and top tracks.

```python
artist = client.get_artist("Kanye West")
artist.name           # "Kanye West"
artist.real_name      # "Ye"
artist.aliases        # ["Yeezy", "Yeezus"]
artist.groups         # ["¥$", "Sunday Service Choir", ...]
artist.total_samples  # 3941
artist.total_covers   # 406
artist.total_remixes  # 244
artist.tracks         # List[Track] — top tracks
```
</details>

<details>
<summary><b>get_artist_by_url(url: str) → ArtistDetail</b></summary>

Get artist details from a full or relative WhoSampled URL.

```python
artist = client.get_artist_by_url("/Kanye-West/")
```
</details>

### Context manager

```python
with WhoSampledClient(delay=2.0) as client:
    samples = client.get_all_samples("Wu-Tang Clan", "C.R.E.A.M.")
```

The session closes automatically on exit.

## Data Models

<details>
<summary><b>Track</b></summary>

```python
track.title             # str
track.artist            # str
track.featured_artists  # List[str]
track.year              # Optional[str]
track.album             # Optional[str]
track.label             # Optional[str]
track.genre             # Optional[str]
track.tags              # List[str]
track.producers         # List[str]
track.url               # str — full WhoSampled URL
track.track_url         # str — relative path (e.g. "/Kanye-West/Bound-2/")
track.image_url         # Optional[str] — album art URL
track.full_title        # str — "Bound 2 by Kanye West feat. Charlie Wilson" (property)
```

Also includes connection count fields (`contains_samples_count`, `sampled_in_count`, `covered_in_count`, `remixed_in_count`) populated from the detail page.
</details>

<details>
<summary><b>Sample</b></summary>

```python
sample.track_title   # str
sample.artist        # str
sample.year          # Optional[str]
sample.sample_type   # Optional[str] — e.g. "Multiple Elements", "Vocals / Lyrics"
sample.url           # Optional[str] — link to connection detail page
sample.image_url     # Optional[str] — thumbnail URL
```
</details>

<details>
<summary><b>ArtistDetail</b></summary>

```python
artist.name            # str
artist.real_name       # Optional[str]
artist.aliases         # List[str]
artist.groups          # List[str]
artist.past_groups     # List[str]
artist.url             # Optional[str]
artist.image_url       # Optional[str]
artist.tracks          # List[Track]
artist.total_samples   # Optional[int]
artist.total_covers    # Optional[int]
artist.total_remixes   # Optional[int]
```
</details>

## Exceptions

| Exception | Description |
|---|---|
| `WhoSampledError` | Base exception for all library errors |
| `TrackNotFoundError` | Track page returned 404 |
| `ArtistNotFoundError` | Artist page returned 404 |
| `PageNotFoundError` | Generic 404 from WhoSampled |
| `RateLimitError` | Rate limited (HTTP 429) after all retries |
| `ParseError` | HTML structure did not match expected layout |

All exceptions inherit from `WhoSampledError`.

## Examples

### Find all samples of a song

```python
from whosampled import WhoSampledClient

with WhoSampledClient(delay=2.0) as client:
    samples = client.get_all_samples("Wu-Tang Clan", "C.R.E.A.M.")
    for s in samples:
        print(f"{s.track_title} by {s.artist} ({s.year}) — {s.sample_type}")
```

### Search and get details in one go

```python
from whosampled import WhoSampledClient

client = WhoSampledClient()
tracks = client.search_tracks("Madonna Vogue")

for track in tracks:
    print(f"\n{track.full_title}")
    detail = client.get_track_detail_by_url(track.url)
    for s in detail.samples:
        print(f"  samples: {s.track_title} by {s.artist}")
```

### Get artist statistics

```python
from whosampled import WhoSampledClient

client = WhoSampledClient()
artist = client.get_artist("J Dilla")
print(f"{artist.name}: {artist.total_samples} samples, {artist.total_covers} covers, {artist.total_remixes} remixes")
```

## Rate Limiting

WhoSampled may rate-limit or block IP addresses that send too many requests. The library includes a configurable delay between requests (default: 1 second). For bulk operations, increase the delay:

```python
client = WhoSampledClient(delay=2.0)  # 2 seconds between requests
```

## Disclaimer

This library scrapes WhoSampled's public web pages. It is not affiliated with or endorsed by WhoSampled. Be respectful of their servers by using reasonable delays between requests. Consider supporting WhoSampled by visiting their site and contributing to their community.

## License

MIT
