mirror of
https://github.com/vinta/awesome-python.git
synced 2026-11-08 02:15:12 -06:00
Merge branch 'fix/sponsorship-funnel'
This commit is contained in:
@@ -162,7 +162,7 @@ The component vocabulary is small and table-led. Source of truth: `website/stati
|
||||
- **Hero**. Magazine-cover headline, dark earthy ground, kicker and proof microcopy, primary CTA button using `--hero-btn-start` / `--hero-btn-end`. Subtle grid plus slow sheen. Respects `prefers-reduced-motion`.
|
||||
- **Jump links**. One per use case in a category hero, plus one for the guide. Underlined like the intro's links, with a trailing ↓. Never tag-styled: tags filter the table or open another page, jump links only scroll.
|
||||
- **Guide band**. Everything in a category intro after its "How to choose" list. It sits below the table on the hero's dark gradient, with its H2 above the text.
|
||||
- **Sponsor band**. Sits below the hero on the home page, on `--bg-paper-strong`. Editorial layout, not a logo wall. Sponsor links share the global accent treatment.
|
||||
- **Sponsor band**. Sits below the hero on the home page and category pages, on `--bg-paper-strong`. Editorial layout, not a logo wall. Sponsor links share the global accent treatment.
|
||||
- **CTA**. Warm `--cta-bg`, full-bleed within shell. The button itself uses accent tokens.
|
||||
- **Footer**. Dark warm charcoal, part of the same system. Footer links share the global hover and focus treatment.
|
||||
- **Search**. Pill input with `--search-inset` interior and `--search-focus-ring` focus ring. Focus shadow uses `--search-focus-shadow`.
|
||||
|
||||
@@ -6,9 +6,9 @@ An opinionated guide to the best Python frameworks, libraries, and tools.
|
||||
|
||||
## **Sponsors**
|
||||
|
||||
- [iPulse AI](https://ipulseai.com) - Open Agentic Investment Research Platform. Inspect deep multi-agent market forecasts and stock picks.
|
||||
- [iPulse AI](https://ipulseai.com?utm_source=awesome-python&utm_medium=sponsorship&utm_campaign=sponsor&utm_content=readme) - Open Agentic Investment Research Platform. Inspect deep multi-agent market forecasts and stock picks.
|
||||
|
||||
> The **#10 most-starred repo on GitHub**. Put your product in front of Python developers. [Become a sponsor](SPONSORSHIP.md).
|
||||
> **Sponsor slots open**, from $99/month. The **#10 most-starred repo on GitHub**. Put your product in front of Python developers. [Become a sponsor](https://awesome-python.com/sponsorship/).
|
||||
|
||||
## Categories
|
||||
|
||||
|
||||
+10
-8
@@ -21,17 +21,17 @@ Your sponsorship puts your product in front of developers at the exact moment th
|
||||
|
||||
## Sponsorship Tiers
|
||||
|
||||
### Headline Sponsor - $500/month
|
||||
### Headline Sponsor - $500/month or $4,500/year
|
||||
|
||||
- Large logo and one-line description (max 120 characters) pinned at the very top of the README, above all project entries
|
||||
- Logo link in the sponsor section of [awesome-python.com](https://awesome-python.com/)
|
||||
|
||||
### Featured Sponsor - $200/month
|
||||
### Featured Sponsor - $200/month or $1,800/year
|
||||
|
||||
- Text entry (`[Name](URL) - Description.`, max 120 characters) pinned at the top of the README, directly below Headline sponsors
|
||||
- Text link in the sponsor section of [awesome-python.com](https://awesome-python.com/)
|
||||
- Small logo and text entry (`[Name](URL) - Description.`, max 120 characters) pinned at the top of the README, directly below Headline sponsors
|
||||
- Logo and text link in the sponsor section of [awesome-python.com](https://awesome-python.com/)
|
||||
|
||||
### Indie Dev Sponsor - $99/month or $249/quarter
|
||||
### Indie Dev Sponsor - $99/month, $249/quarter, or $891/year
|
||||
|
||||
- Lower-cost tier for indie developers, solo founders, and one-person companies only
|
||||
- Text entry (`[Name](URL) - Description.`, max 120 characters) in the README sponsor section, directly below Featured sponsors
|
||||
@@ -43,13 +43,15 @@ Indie Dev Sponsor is available only to founder-led (one-person company) products
|
||||
|
||||
Email [sponsorship@awesome-python.com](mailto:sponsorship@awesome-python.com?subject=Awesome%20Python%20Sponsorship) with:
|
||||
|
||||
- **Tier:** Headline Sponsor ($500/mo), Featured Sponsor ($200/mo), or Indie Dev Sponsor ($99/mo or $249/quarter)
|
||||
- **Content:** Product name, URL, logo, and description (Headline tier) or `[Name](URL) - Description.` entry (Featured or Indie Dev tier)
|
||||
- **Duration:** 1, 3, 6 months, or longer
|
||||
- **Tier:** Headline Sponsor ($500/mo or $4,500/year), Featured Sponsor ($200/mo or $1,800/year), or Indie Dev Sponsor ($99/mo, $249/quarter, or $891/year)
|
||||
- **Content:** Product name, URL, logo, and description (Headline tier), logo and `[Name](URL) - Description.` entry (Featured tier), or `[Name](URL) - Description.` entry (Indie Dev tier)
|
||||
- **Duration:** 1, 3, or 6 months, or 1 year (3 months free)
|
||||
- **Payment method:** card via [Open Collective](https://opencollective.com/awesome-python), US bank transfer (ACH/wire), or PayPal
|
||||
|
||||
One upfront payment per term. Setup takes less than 24 hours.
|
||||
|
||||
Sponsor links carry UTM tags, so clicks show up in your own analytics as `awesome-python / sponsorship`.
|
||||
|
||||
## Previously Sponsored By
|
||||
|
||||
- [Warp](https://www.warp.dev/) - The terminal for modern developers.
|
||||
|
||||
@@ -11,6 +11,7 @@ from collections.abc import Sequence
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import TypedDict
|
||||
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from markdown_it import MarkdownIt
|
||||
@@ -130,6 +131,16 @@ def extract_github_repo(url: str) -> str | None:
|
||||
return m.group(1) if m else None
|
||||
|
||||
|
||||
def sponsor_website_url(url: str) -> str:
|
||||
"""Set utm_content=website so sponsors can tell website clicks from README clicks."""
|
||||
parts = urlsplit(url)
|
||||
pairs = parse_qsl(parts.query, keep_blank_values=True)
|
||||
if not any(k == "utm_content" for k, _ in pairs):
|
||||
return url
|
||||
query = [(k, "website" if k == "utm_content" else v) for k, v in pairs]
|
||||
return urlunsplit(parts._replace(query=urlencode(query)))
|
||||
|
||||
|
||||
def load_stars(path: Path) -> dict[str, dict]:
|
||||
"""Load star data from JSON. Returns empty dict if file doesn't exist or is corrupt."""
|
||||
if path.exists():
|
||||
@@ -749,6 +760,8 @@ def build(repo_root: Path) -> None:
|
||||
|
||||
parsed_groups = parse_readme(readme_text)
|
||||
sponsors = parse_sponsors(readme_text)
|
||||
for sponsor in sponsors:
|
||||
sponsor["url"] = sponsor_website_url(sponsor["url"])
|
||||
|
||||
categories = [cat for g in parsed_groups for cat in g["categories"]]
|
||||
cat_slugs = [cat["slug"] for cat in categories]
|
||||
@@ -881,6 +894,7 @@ def build(repo_root: Path) -> None:
|
||||
parent_category=parent_category,
|
||||
group_categories=group_categories,
|
||||
category_json_ld=category_json_ld,
|
||||
sponsors=sponsors,
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
@@ -399,6 +399,13 @@ document.addEventListener("click", function (e) {
|
||||
}
|
||||
});
|
||||
|
||||
// Mailto clicks leave no page view, so the sponsor funnel needs its own event
|
||||
document.addEventListener("click", function (e) {
|
||||
const link = e.target.closest('a[href^="mailto:sponsorship@"]');
|
||||
if (!link || typeof gtag !== "function") return;
|
||||
gtag("event", "sponsor_contact", { tier: link.dataset.tier });
|
||||
});
|
||||
|
||||
if (location.hash) {
|
||||
const linkedRow = document.getElementById(decodeURIComponent(location.hash.slice(1)));
|
||||
if (linkedRow && linkedRow.matches("tr.row")) openEntryRow(linkedRow);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
{% if sponsors %}
|
||||
<section class="sponsor-band" aria-labelledby="sponsor-heading" data-reveal>
|
||||
<div class="section-shell sponsor-shell">
|
||||
<header class="sponsor-meta">
|
||||
<p class="section-label" id="sponsor-heading">Sponsors</p>
|
||||
<a class="sponsor-become" href="/sponsorship/"> Become a sponsor </a>
|
||||
</header>
|
||||
<ul class="sponsor-list">
|
||||
{% for sponsor in sponsors %}
|
||||
<li class="sponsor">
|
||||
<a
|
||||
class="sponsor-link"
|
||||
href="{{ sponsor.url }}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<span class="sponsor-name">{{ sponsor.name }}</span>
|
||||
</a>
|
||||
<p class="sponsor-desc">{{ sponsor.description | safe }}</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
@@ -208,6 +208,8 @@
|
||||
<td></td>
|
||||
</tr>
|
||||
{% endmacro %}
|
||||
{% include "_sponsor_band.html" %}
|
||||
|
||||
<section class="results-section" id="library-index">
|
||||
<div class="results-intro section-shell" data-reveal>
|
||||
{% if entry_groups %}
|
||||
|
||||
@@ -70,31 +70,7 @@
|
||||
</header>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% if sponsors %}
|
||||
<section class="sponsor-band" aria-labelledby="sponsor-heading" data-reveal>
|
||||
<div class="section-shell sponsor-shell">
|
||||
<header class="sponsor-meta">
|
||||
<p class="section-label" id="sponsor-heading">Sponsors</p>
|
||||
<a class="sponsor-become" href="/sponsorship/"> Become a sponsor </a>
|
||||
</header>
|
||||
<ul class="sponsor-list">
|
||||
{% for sponsor in sponsors %}
|
||||
<li class="sponsor">
|
||||
<a
|
||||
class="sponsor-link"
|
||||
href="{{ sponsor.url }}"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<span class="sponsor-name">{{ sponsor.name }}</span>
|
||||
</a>
|
||||
<p class="sponsor-desc">{{ sponsor.description | safe }}</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
{% include "_sponsor_band.html" %}
|
||||
|
||||
<section class="results-section" id="library-index">
|
||||
<div class="results-intro section-shell" data-reveal>
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
<p class="category-subtitle">
|
||||
The #10 most-starred repository on GitHub, and the list Python
|
||||
developers check when choosing what to use. Your sponsorship puts your
|
||||
product in front of them at the moment of decision.
|
||||
product in front of them at the moment of decision. Headline $500,
|
||||
Featured $200, Indie Dev from $99 a month.
|
||||
</p>
|
||||
|
||||
{% if hero_stats %}
|
||||
@@ -45,6 +46,7 @@
|
||||
<div class="hero-actions">
|
||||
<a
|
||||
href="mailto:sponsorship@awesome-python.com?subject=Awesome%20Python%20Sponsorship"
|
||||
data-tier="general"
|
||||
class="hero-action hero-action-primary"
|
||||
>Email sponsorship@awesome-python.com</a
|
||||
>
|
||||
@@ -114,7 +116,7 @@
|
||||
<p class="tier-eyebrow">Headline Sponsor</p>
|
||||
<p class="tier-price">
|
||||
<span class="tier-amount">$500</span>
|
||||
<span class="tier-cadence">/ month</span>
|
||||
<span class="tier-cadence">/ month or $4,500 / year</span>
|
||||
</p>
|
||||
<p class="tier-summary">
|
||||
Logo pinned at the top of the README. Logo on the website.
|
||||
@@ -128,6 +130,7 @@
|
||||
</ul>
|
||||
<a
|
||||
href="mailto:sponsorship@awesome-python.com?subject=Awesome%20Python%20Sponsorship%20-%20Headline%20Tier"
|
||||
data-tier="headline"
|
||||
class="tier-cta"
|
||||
>Email about Headline tier</a
|
||||
>
|
||||
@@ -136,21 +139,25 @@
|
||||
<p class="tier-eyebrow">Featured Sponsor</p>
|
||||
<p class="tier-price">
|
||||
<span class="tier-amount">$200</span>
|
||||
<span class="tier-cadence">/ month</span>
|
||||
<span class="tier-cadence">/ month or $1,800 / year</span>
|
||||
</p>
|
||||
<p class="tier-summary">
|
||||
Text link pinned at the top of the README. Text link on the website.
|
||||
Small logo and text link pinned at the top of the README. Logo and
|
||||
text link on the website.
|
||||
</p>
|
||||
<ul class="tier-includes">
|
||||
<li>
|
||||
Text entry (<code>[Name](URL) - Description.</code>, max 120
|
||||
characters) pinned at the top of the README, directly below
|
||||
Headline sponsors.
|
||||
Small logo and text entry
|
||||
(<code>[Name](URL) - Description.</code>, max 120 characters)
|
||||
pinned at the top of the README, directly below Headline sponsors.
|
||||
</li>
|
||||
<li>
|
||||
Logo and text link in the sponsor section of awesome-python.com.
|
||||
</li>
|
||||
<li>Text link in the sponsor section of awesome-python.com.</li>
|
||||
</ul>
|
||||
<a
|
||||
href="mailto:sponsorship@awesome-python.com?subject=Awesome%20Python%20Sponsorship%20-%20Featured%20Tier"
|
||||
data-tier="featured"
|
||||
class="tier-cta"
|
||||
>Email about Featured tier</a
|
||||
>
|
||||
@@ -159,7 +166,7 @@
|
||||
<p class="tier-eyebrow">Indie Dev Sponsor</p>
|
||||
<p class="tier-price">
|
||||
<span class="tier-amount">$99</span>
|
||||
<span class="tier-cadence">/ month or $249 / quarter</span>
|
||||
<span class="tier-cadence">/ month, $249 / quarter, or $891 / year</span>
|
||||
</p>
|
||||
<p class="tier-summary">
|
||||
Lower-cost text link for eligible indie developers and solo
|
||||
@@ -180,6 +187,7 @@
|
||||
</ul>
|
||||
<a
|
||||
href="mailto:sponsorship@awesome-python.com?subject=Awesome%20Python%20Sponsorship%20-%20Indie%20Dev%20Tier"
|
||||
data-tier="indie"
|
||||
class="tier-cta"
|
||||
>Email about Indie Dev tier</a
|
||||
>
|
||||
@@ -225,6 +233,7 @@
|
||||
Email
|
||||
<a
|
||||
href="mailto:sponsorship@awesome-python.com?subject=Awesome%20Python%20Sponsorship"
|
||||
data-tier="general"
|
||||
>sponsorship@awesome-python.com</a
|
||||
>
|
||||
with the four items below.
|
||||
@@ -233,21 +242,22 @@
|
||||
<div>
|
||||
<dt>Tier</dt>
|
||||
<dd>
|
||||
Headline Sponsor ($500/mo), Featured Sponsor ($200/mo), or Indie Dev
|
||||
Sponsor ($99/mo or $249/quarter).
|
||||
Headline Sponsor ($500/mo or $4,500/year), Featured Sponsor
|
||||
($200/mo or $1,800/year), or Indie Dev Sponsor ($99/mo, $249/quarter,
|
||||
or $891/year).
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Content</dt>
|
||||
<dd>
|
||||
Product name, URL, logo, and description (Headline tier), or
|
||||
<code>[Name](URL) - Description.</code> entry (Featured or Indie Dev
|
||||
tier).
|
||||
Product name, URL, logo, and description (Headline tier), logo and
|
||||
<code>[Name](URL) - Description.</code> entry (Featured tier), or
|
||||
<code>[Name](URL) - Description.</code> entry (Indie Dev tier).
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Duration</dt>
|
||||
<dd>1, 3, 6 months, or longer.</dd>
|
||||
<dd>1, 3, or 6 months, or 1 year (3 months free).</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Payment method</dt>
|
||||
@@ -261,6 +271,7 @@
|
||||
<div class="sponsorship-cta-row">
|
||||
<a
|
||||
href="mailto:sponsorship@awesome-python.com?subject=Awesome%20Python%20Sponsorship"
|
||||
data-tier="general"
|
||||
class="hero-action hero-action-primary"
|
||||
>Email sponsorship@awesome-python.com</a
|
||||
>
|
||||
|
||||
@@ -26,6 +26,7 @@ from build import (
|
||||
load_pypi_badges,
|
||||
load_stars,
|
||||
sort_entries,
|
||||
sponsor_website_url,
|
||||
subcategory_path,
|
||||
)
|
||||
from readme_parser import parse_readme, slugify
|
||||
@@ -458,6 +459,68 @@ class TestBuild:
|
||||
assert llms_txt != readme
|
||||
assert "# Contributing" not in llms_txt
|
||||
|
||||
def test_homepage_sponsor_link_tags_website_clicks(self, tmp_path):
|
||||
readme = textwrap.dedent("""\
|
||||
# Awesome Python
|
||||
|
||||
Intro.
|
||||
|
||||
## **Sponsors**
|
||||
|
||||
- [Sponsor](https://sponsor.example.com/?ref=x&utm_source=awesome-python&utm_content=readme) - Sponsored tool.
|
||||
|
||||
## Projects
|
||||
|
||||
**Tools**
|
||||
|
||||
## Widgets
|
||||
|
||||
- [w1](https://example.com) - A widget.
|
||||
|
||||
# Contributing
|
||||
|
||||
Help!
|
||||
""")
|
||||
(tmp_path / "README.md").write_text(readme, encoding="utf-8")
|
||||
self._copy_real_templates(tmp_path)
|
||||
|
||||
build(tmp_path)
|
||||
|
||||
index_html = (tmp_path / "website" / "output" / "index.html").read_text(encoding="utf-8")
|
||||
assert 'href="https://sponsor.example.com/?ref=x&utm_source=awesome-python&utm_content=website"' in index_html
|
||||
assert "utm_content=readme" not in index_html
|
||||
|
||||
def test_category_page_shows_sponsor_band(self, tmp_path):
|
||||
readme = textwrap.dedent("""\
|
||||
# Awesome Python
|
||||
|
||||
Intro.
|
||||
|
||||
## **Sponsors**
|
||||
|
||||
- [Sponsor](https://sponsor.example.com) - Sponsored tool.
|
||||
|
||||
## Projects
|
||||
|
||||
**Tools**
|
||||
|
||||
## Widgets
|
||||
|
||||
- [w1](https://example.com) - A widget.
|
||||
|
||||
# Contributing
|
||||
|
||||
Help!
|
||||
""")
|
||||
(tmp_path / "README.md").write_text(readme, encoding="utf-8")
|
||||
self._copy_real_templates(tmp_path)
|
||||
|
||||
build(tmp_path)
|
||||
|
||||
category_html = (tmp_path / "website" / "output" / "categories" / "widgets" / "index.html").read_text(encoding="utf-8")
|
||||
assert 'href="https://sponsor.example.com"' in category_html
|
||||
assert '<span class="sponsor-name">Sponsor</span>' in category_html
|
||||
|
||||
def test_build_cleans_stale_output(self, tmp_path):
|
||||
readme = textwrap.dedent("""\
|
||||
# T
|
||||
@@ -1278,6 +1341,23 @@ class TestExtractGithubRepo:
|
||||
assert extract_github_repo("https://github.com/org") is None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# sponsor_website_url
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestSponsorWebsiteUrl:
|
||||
def test_replaces_readme_utm_content(self):
|
||||
url = "https://example.com?utm_source=awesome-python&utm_medium=sponsorship&utm_campaign=sponsor&utm_content=readme"
|
||||
assert sponsor_website_url(url) == "https://example.com?utm_source=awesome-python&utm_medium=sponsorship&utm_campaign=sponsor&utm_content=website"
|
||||
|
||||
def test_keeps_path_and_other_params(self):
|
||||
assert sponsor_website_url("https://example.com/landing?ref=x&utm_content=readme") == "https://example.com/landing?ref=x&utm_content=website"
|
||||
|
||||
def test_leaves_untagged_url_unchanged(self):
|
||||
assert sponsor_website_url("https://example.com") == "https://example.com"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# load_stars
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user