Add svelte-meta-tags for improved SEO and metadata management across multiple pages. Update package.json and pnpm-lock.yaml to include svelte-meta-tags dependency. Clean up whitespace in API functions for better code readability.

This commit is contained in:
Luke Hagar
2025-08-14 20:18:05 -05:00
parent a42d5a1d06
commit f4b060dae5
10 changed files with 174 additions and 238 deletions

View File

@@ -42,6 +42,7 @@
"prisma": "^6.13.0",
"svelte": "^5.37.3",
"svelte-check": "^4.3.1",
"svelte-meta-tags": "^4.4.0",
"tailwindcss": "^4.1.11",
"typescript": "^5.9.2",
"vite": "^7.0.6"

72
pnpm-lock.yaml generated
View File

@@ -36,6 +36,9 @@ importers:
specifier: ^5.7.0
version: 5.7.0
devDependencies:
'@scalar/sveltekit':
specifier: ^0.1.17
version: 0.1.17(@sveltejs/kit@2.27.0(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.37.3)(vite@7.0.6(@types/node@24.2.0)(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.37.3)(vite@7.0.6(@types/node@24.2.0)(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.37.3)
'@sveltejs/adapter-auto':
specifier: ^6.0.1
version: 6.0.1(@sveltejs/kit@2.27.0(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.37.3)(vite@7.0.6(@types/node@24.2.0)(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.37.3)(vite@7.0.6(@types/node@24.2.0)(jiti@2.5.1)(lightningcss@1.30.1)))
@@ -75,6 +78,9 @@ importers:
svelte-check:
specifier: ^4.3.1
version: 4.3.1(picomatch@4.0.3)(svelte@5.37.3)(typescript@5.9.2)
svelte-meta-tags:
specifier: ^4.4.0
version: 4.4.0(svelte@5.37.3)
tailwindcss:
specifier: ^4.1.11
version: 4.1.11
@@ -500,6 +506,25 @@ packages:
cpu: [x64]
os: [win32]
'@scalar/core@0.3.11':
resolution: {integrity: sha512-4O3mC29k2STz4quBHrpsl6czxi94F3dy8Ej/OPbELS2myKO4GoxromT0BiKSLPAThCnmJ0rqMZ7k9NsgzVjEgA==}
engines: {node: '>=20'}
'@scalar/openapi-types@0.3.7':
resolution: {integrity: sha512-QHSvHBVDze3+dUwAhIGq6l1iOev4jdoqdBK7QpfeN1Q4h+6qpVEw3EEqBiH0AXUSh/iWwObBv4uMgfIx0aNZ5g==}
engines: {node: '>=20'}
'@scalar/sveltekit@0.1.17':
resolution: {integrity: sha512-4K0+f+OEI+HqrAnB3A5lX4ZnWoVQVxjALSz6xdRXzo8MIj/OJAOuAgWY8U8wwePNFmjWJDGkRfd/UI1g4vRm2w==}
engines: {node: '>=20'}
peerDependencies:
'@sveltejs/kit': ^2.25.0
svelte: ^5.0.0
'@scalar/types@0.2.11':
resolution: {integrity: sha512-SUZzGmoisWsYv33LmmT/ajvSlcl9ZDj9d5RncJ+wB9ZQ2l018xlfpDIH9Kdfo+6KCKQOe3LYLXfH4Lzm891Mag==}
engines: {node: '>=20'}
'@standard-schema/spec@1.0.0':
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
@@ -1209,6 +1234,11 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
nanoid@5.1.5:
resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==}
engines: {node: ^18 || >=20}
hasBin: true
napi-build-utils@2.0.0:
resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
@@ -1429,6 +1459,9 @@ packages:
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
schema-dts@1.1.5:
resolution: {integrity: sha512-RJr9EaCmsLzBX2NDiO5Z3ux2BVosNZN5jo0gWgsyKvxKIUL5R3swNvoorulAeL9kLB0iTSX7V6aokhla2m7xbg==}
semver@7.7.2:
resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
engines: {node: '>=10'}
@@ -1491,6 +1524,11 @@ packages:
svelte: ^4.0.0 || ^5.0.0-next.0
typescript: '>=5.0.0'
svelte-meta-tags@4.4.0:
resolution: {integrity: sha512-0g7sksBXdCGYcNM44uipqhVwDrtImB73iZdcpWHE0q0+k96Zg0WS6ySPAV+gX34DSqrkrvcqkG/tI2lwN1KbbA==}
peerDependencies:
svelte: ^5.0.0
svelte@5.37.3:
resolution: {integrity: sha512-7t/ejshehHd+95z3Z7ebS7wsqHDQxi/8nBTuTRwpMgNegfRBfuitCSKTUDKIBOExqfT2+DhQ2VLG8Xn+cBXoaQ==}
engines: {node: '>=18'}
@@ -1633,6 +1671,9 @@ packages:
zimmerframe@1.1.2:
resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==}
zod@3.24.1:
resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==}
snapshots:
'@ampproject/remapping@2.3.0':
@@ -1940,6 +1981,26 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.46.2':
optional: true
'@scalar/core@0.3.11':
dependencies:
'@scalar/types': 0.2.11
'@scalar/openapi-types@0.3.7':
dependencies:
zod: 3.24.1
'@scalar/sveltekit@0.1.17(@sveltejs/kit@2.27.0(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.37.3)(vite@7.0.6(@types/node@24.2.0)(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.37.3)(vite@7.0.6(@types/node@24.2.0)(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.37.3)':
dependencies:
'@scalar/core': 0.3.11
'@sveltejs/kit': 2.27.0(@sveltejs/vite-plugin-svelte@6.1.0(svelte@5.37.3)(vite@7.0.6(@types/node@24.2.0)(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.37.3)(vite@7.0.6(@types/node@24.2.0)(jiti@2.5.1)(lightningcss@1.30.1))
svelte: 5.37.3
'@scalar/types@0.2.11':
dependencies:
'@scalar/openapi-types': 0.3.7
nanoid: 5.1.5
zod: 3.24.1
'@standard-schema/spec@1.0.0': {}
'@sveltejs/acorn-typescript@1.0.5(acorn@8.15.0)':
@@ -2624,6 +2685,8 @@ snapshots:
nanoid@3.3.11: {}
nanoid@5.1.5: {}
napi-build-utils@2.0.0: {}
node-abi@3.75.0:
@@ -2833,6 +2896,8 @@ snapshots:
safe-buffer@5.2.1: {}
schema-dts@1.1.5: {}
semver@7.7.2: {}
set-cookie-parser@2.7.1: {}
@@ -2895,6 +2960,11 @@ snapshots:
transitivePeerDependencies:
- picomatch
svelte-meta-tags@4.4.0(svelte@5.37.3):
dependencies:
schema-dts: 1.1.5
svelte: 5.37.3
svelte@5.37.3:
dependencies:
'@ampproject/remapping': 2.3.0
@@ -3026,3 +3096,5 @@ snapshots:
yallist@5.0.0: {}
zimmerframe@1.1.2: {}
zod@3.24.1: {}

View File

@@ -25,7 +25,7 @@ export type Results = {
export async function getRecentDownloads(packageName: string, category?: string): Promise<Results[]> {
const cacheKey = CacheManager.getRecentStatsKey(packageName);
// Ensure DB has fresh data for this package before computing recent
if (!category) {
await ensurePackageFreshnessFor(packageName);
@@ -49,7 +49,7 @@ export async function getRecentDownloads(packageName: string, category?: string)
downloads: r._sum.downloads || 0
}));
}
// Default: return day/week/month computed on the fly
const day: Results[] = await getRecentDownloads(packageName, 'day');
const week: Results[] = await getRecentDownloads(packageName, 'week');
@@ -62,7 +62,7 @@ export async function getRecentDownloads(packageName: string, category?: string)
} else {
await cache.del(cacheKey);
}
return result;
}
@@ -84,20 +84,20 @@ function getRecentBounds(category: string) {
export async function getOverallDownloads(packageName: string, mirrors?: string) {
const cacheKey = CacheManager.getPackageKey(packageName, `overall_${mirrors || 'all'}`);
// Always ensure DB freshness first to avoid returning stale cache
await ensurePackageFreshnessFor(packageName);
const whereClause: any = {
package: packageName
};
if (mirrors === 'true') {
whereClause.category = 'with_mirrors';
} else if (mirrors === 'false') {
whereClause.category = 'without_mirrors';
}
const result = await prisma.overallDownloadCount.findMany({
where: whereClause,
orderBy: {
@@ -111,24 +111,24 @@ export async function getOverallDownloads(packageName: string, mirrors?: string)
} else {
await cache.del(cacheKey);
}
return result;
}
export async function getPythonMajorDownloads(packageName: string, version?: string) {
const cacheKey = CacheManager.getPackageKey(packageName, `python_major_${version || 'all'}`);
// Ensure DB freshness first
await ensurePackageFreshnessFor(packageName);
const whereClause: any = {
package: packageName
};
if (version) {
whereClause.category = version;
}
const result = await prisma.pythonMajorDownloadCount.findMany({
where: whereClause,
orderBy: {
@@ -141,24 +141,24 @@ export async function getPythonMajorDownloads(packageName: string, version?: str
} else {
await cache.del(cacheKey);
}
return result;
}
export async function getPythonMinorDownloads(packageName: string, version?: string) {
const cacheKey = CacheManager.getPackageKey(packageName, `python_minor_${version || 'all'}`);
// Ensure DB freshness first
await ensurePackageFreshnessFor(packageName);
const whereClause: any = {
package: packageName
};
if (version) {
whereClause.category = version;
}
const result = await prisma.pythonMinorDownloadCount.findMany({
where: whereClause,
orderBy: {
@@ -171,24 +171,24 @@ export async function getPythonMinorDownloads(packageName: string, version?: str
} else {
await cache.del(cacheKey);
}
return result;
}
export async function getSystemDownloads(packageName: string, os?: string) {
const cacheKey = CacheManager.getPackageKey(packageName, `system_${os || 'all'}`);
// Ensure DB freshness first
await ensurePackageFreshnessFor(packageName);
const whereClause: any = {
package: packageName
};
if (os) {
whereClause.category = os;
}
const result = await prisma.systemDownloadCount.findMany({
where: whereClause,
orderBy: {
@@ -201,7 +201,7 @@ export async function getSystemDownloads(packageName: string, os?: string) {
} else {
await cache.del(cacheKey);
}
return result;
}
@@ -330,23 +330,18 @@ export async function searchPackages(searchTerm: string) {
}
export async function getPackageCount() {
try {
// First try recent monthly snapshot as authoritative
const recent = await prisma.recentDownloadCount.findMany({
where: { category: 'month' },
distinct: ['package'],
select: { package: true }
});
// First try recent monthly snapshot as authoritative
const recent = await prisma.recentDownloadCount.findMany({
where: { category: 'month' },
distinct: ['package'],
select: { package: true }
});
const distinct = new Set<string>(recent.map((r) => r.package));
const distinct = new Set<string>(recent.map((r) => r.package));
const count = distinct.size;
const count = distinct.size;
return count;
} catch (error) {
console.error('getPackageCount failed:', error);
return undefined
}
return count;
}
export async function getPopularPackages(limit = 10, days = 30): Promise<Array<{ package: string; downloads: number }>> {
@@ -410,7 +405,7 @@ export async function getPackageMetadata(packageName: string): Promise<PackageMe
return !max || new Date(t).getTime() > new Date(max).getTime() ? t : max;
}, null as string | null);
latestReleaseDate = latest ? new Date(latest).toISOString().split('T')[0] : null;
} catch {}
} catch { }
return {
name: packageName,
version,
@@ -445,7 +440,7 @@ export async function invalidatePackageCache(packageName: string) {
CacheManager.getPackageKey(packageName, 'python_minor_all'),
CacheManager.getPackageKey(packageName, 'system_all'),
];
for (const pattern of patterns) {
await cache.del(pattern);
}

View File

@@ -18,6 +18,9 @@
<a href="/" class="border-transparent text-gray-300 hover:border-gray-600 hover:text-white inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
Home
</a>
<a href="/search" class="border-transparent text-gray-300 hover:border-gray-600 hover:text-white inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
Search
</a>
<a href="/about" class="border-transparent text-gray-300 hover:border-gray-600 hover:text-white inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium">
About
</a>

View File

@@ -1,16 +1,18 @@
<script lang="ts">
import type { PageData } from './$types';
import LoadingSpinner from '$lib/components/LoadingSpinner.svelte';
import { MetaTags } from 'svelte-meta-tags';
const { data } = $props<{ data: PageData }>();
const { data }: { data: PageData } = $props();
let searchTerm = $state('');
</script>
<svelte:head>
<title>PyPI Stats - Download statistics for Python packages</title>
<meta name="description" content="Get download statistics for Python packages from PyPI" />
</svelte:head>
<MetaTags
title="PyPI Stats - Download Statistics for Python Packages"
description="Get comprehensive download statistics for Python packages from PyPI. Track package popularity, Python version usage, system breakdowns, and more with our free API."
keywords={["PyPI", "Python packages", "download statistics", "package analytics", "Python package stats", "PyPI downloads"]}
/>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div class="text-center">

View File

@@ -1,7 +1,12 @@
<svelte:head>
<title>About - PyPI Stats</title>
<meta name="description" content="Learn about PyPI Stats and how it works" />
</svelte:head>
<script lang="ts">
import { MetaTags } from 'svelte-meta-tags';
</script>
<MetaTags
title="About PyPI Stats - Download Statistics for Python Packages"
description="Learn about PyPI Stats, how we collect download data from PyPI, and what statistics we provide for Python packages."
keywords={["PyPI Stats", "about", "data collection", "Python package statistics", "PyPI downloads", "BigQuery"]}
/>
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<h1 class="mb-8 text-3xl font-bold text-gray-100">About PyPI Stats</h1>

View File

@@ -1,9 +1,14 @@
<svelte:head>
<title>API Documentation - PyPI Stats</title>
<meta name="description" content="API documentation for PyPI Stats" />
</svelte:head>
<script lang="ts">
import { MetaTags } from 'svelte-meta-tags';
</script>
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<MetaTags
title="API Documentation - PyPI Stats"
description="Complete API documentation for PyPI Stats. Learn how to access Python package download statistics programmatically with our free REST API."
keywords={["PyPI Stats API", "API documentation", "REST API", "Python package statistics", "download statistics API", "JSON API"]}
/>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<h1 class="mb-8 text-3xl font-bold text-gray-100">API Documentation</h1>
<div class="prose prose-lg max-w-none">
@@ -14,187 +19,30 @@
<h2 class="mt-8 mb-4 text-2xl font-semibold text-gray-100">Base URL</h2>
<div class="mb-6 rounded-md bg-gray-900 p-4">
<code class="text-sm text-gray-200">https://pypistats.org/api</code>
<code class="text-sm text-gray-200">https://pypistats.dev/api</code>
</div>
<h2 class="mt-8 mb-4 text-2xl font-semibold text-gray-100">Endpoints</h2>
<div class="space-y-8">
<!-- Chart Data / Images -->
<div class="rounded-lg border border-gray-800 bg-gray-900 p-6">
<h3 class="mb-3 text-xl font-semibold text-gray-100">Chart Data / Images</h3>
<div class="mb-4 rounded-md bg-gray-950 p-4">
<code class="text-sm text-gray-200">GET /api/packages/&#123;package&#125;/chart/&#123;type&#125;</code>
</div>
<p class="mb-4 text-gray-400">
Returns either a PNG chart image (default) or JSON payload for interactive charts.
</p>
<div class="mb-4">
<strong class="text-gray-100">Path params:</strong>
<ul class="mt-2 list-disc pl-6 text-gray-400">
<li><code>type</code>: one of <code>overall</code>, <code>python_major</code>, <code>python_minor</code>, <code>system</code>, <code>installer</code>, <code>version</code></li>
</ul>
</div>
<div class="mb-4">
<strong class="text-gray-100">Query params:</strong>
<ul class="mt-2 list-disc pl-6 text-gray-400">
<li><code>format</code>: <code>json</code> to return the data model instead of an image</li>
<li><code>chart</code>: <code>line</code> (default) or <code>bar</code></li>
<li><code>mirrors</code>: for <code>overall</code>, include mirror downloads (<code>true</code>/<code>false</code>)</li>
<li><code>version</code>: for <code>python_major</code>/<code>python_minor</code> filters</li>
<li><code>os</code>: for <code>system</code> filter (e.g. <code>Linux</code>, <code>Windows</code>, <code>Darwin</code>)</li>
<li><code>nocache</code> / <code>cache</code>: bypass caching (<code>nocache=1</code> or <code>cache=false</code>)</li>
</ul>
</div>
<div class="mb-4">
<strong class="text-gray-100">Example:</strong>
<div class="mt-2 rounded-md bg-gray-950 p-4">
<code class="text-sm text-gray-200">GET /api/packages/numpy/chart/overall?format=json&amp;chart=line&amp;mirrors=true</code>
</div>
</div>
</div>
<!-- Recent Downloads -->
<div class="rounded-lg border border-gray-800 bg-gray-900 p-6">
<h3 class="mb-3 text-xl font-semibold text-gray-100">Recent Downloads</h3>
<div class="mb-4 rounded-md bg-gray-950 p-4">
<code class="text-sm text-gray-200">GET /api/packages/&#123;package&#125;/recent</code>
</div>
<p class="mb-4 text-gray-400">
Get recent download statistics for a package (day, week, month).
</p>
<div class="mb-4">
<strong class="text-gray-100">Parameters:</strong>
<ul class="mt-2 list-disc pl-6 text-gray-400">
<li><code>period</code> (optional): Filter by period (day, week, month)</li>
</ul>
</div>
<div class="mb-4">
<strong class="text-gray-100">Example:</strong>
<div class="mt-2 rounded-md bg-gray-950 p-4">
<code class="text-sm text-gray-200">GET /api/packages/numpy/recent?period=month</code>
</div>
</div>
</div>
<!-- Overall Downloads -->
<div class="rounded-lg border border-gray-800 bg-gray-900 p-6">
<h3 class="mb-3 text-xl font-semibold text-gray-100">Overall Downloads</h3>
<div class="mb-4 rounded-md bg-gray-950 p-4">
<code class="text-sm text-gray-200">GET /api/packages/&#123;package&#125;/overall</code>
</div>
<p class="mb-4 text-gray-400">
Get overall download time series for a package.
</p>
<div class="mb-4">
<strong class="text-gray-100">Parameters:</strong>
<ul class="mt-2 list-disc pl-6 text-gray-400">
<li><code>mirrors</code> (optional): Include mirror downloads (true/false)</li>
</ul>
</div>
<div class="mb-4">
<strong class="text-gray-100">Example:</strong>
<div class="mt-2 rounded-md bg-gray-950 p-4">
<code class="text-sm text-gray-200">GET /api/packages/numpy/overall?mirrors=true</code>
</div>
</div>
</div>
<!-- Python Major -->
<div class="rounded-lg border border-gray-800 bg-gray-900 p-6">
<h3 class="mb-3 text-xl font-semibold text-gray-100">Python Major Version Downloads</h3>
<div class="mb-4 rounded-md bg-gray-950 p-4">
<code class="text-sm text-gray-200">GET /api/packages/&#123;package&#125;/python_major</code>
</div>
<p class="mb-4 text-gray-400">
Get download statistics by Python major version (2.x, 3.x).
</p>
<div class="mb-4">
<strong class="text-gray-100">Parameters:</strong>
<ul class="mt-2 list-disc pl-6 text-gray-400">
<li><code>version</code> (optional): Filter by Python major version (2, 3)</li>
</ul>
</div>
<div class="mb-4">
<strong class="text-gray-100">Example:</strong>
<div class="mt-2 rounded-md bg-gray-950 p-4">
<code class="text-sm text-gray-200">GET /api/packages/numpy/python_major?version=3</code>
</div>
</div>
</div>
<!-- Python Minor -->
<div class="rounded-lg border border-gray-800 bg-gray-900 p-6">
<h3 class="mb-3 text-xl font-semibold text-gray-100">Python Minor Version Downloads</h3>
<div class="mb-4 rounded-md bg-gray-950 p-4">
<code class="text-sm text-gray-200">GET /api/packages/&#123;package&#125;/python_minor</code>
</div>
<p class="mb-4 text-gray-400">
Get download statistics by Python minor version (2.7, 3.6, 3.7, etc.).
</p>
<div class="mb-4">
<strong class="text-gray-100">Parameters:</strong>
<ul class="mt-2 list-disc pl-6 text-gray-400">
<li><code>version</code> (optional): Filter by Python minor version (2.7, 3.6, etc.)</li>
</ul>
</div>
<div class="mb-4">
<strong class="text-gray-100">Example:</strong>
<div class="mt-2 rounded-md bg-gray-950 p-4">
<code class="text-sm text-gray-200">GET /api/packages/numpy/python_minor?version=3.8</code>
</div>
</div>
</div>
<!-- System -->
<div class="rounded-lg border border-gray-800 bg-gray-900 p-6">
<h3 class="mb-3 text-xl font-semibold text-gray-100">System Downloads</h3>
<div class="mb-4 rounded-md bg-gray-950 p-4">
<code class="text-sm text-gray-200">GET /api/packages/&#123;package&#125;/system</code>
</div>
<p class="mb-4 text-gray-400">
Get download statistics by operating system (Windows, Linux, macOS).
</p>
<div class="mb-4">
<strong class="text-gray-100">Parameters:</strong>
<ul class="mt-2 list-disc pl-6 text-gray-400">
<li><code>os</code> (optional): Filter by operating system (Windows, Linux, Darwin)</li>
</ul>
</div>
<div class="mb-4">
<strong class="text-gray-100">Example:</strong>
<div class="mt-2 rounded-md bg-gray-950 p-4">
<code class="text-sm text-gray-200">GET /api/packages/numpy/system?os=Linux</code>
</div>
</div>
</div>
</div>
<h2 class="mt-8 mb-4 text-2xl font-semibold text-gray-100">Response Format</h2>
<p class="mb-4 text-gray-400">
All API endpoints return JSON responses with the following structure:
</p>
<div class="mb-6 rounded-md bg-gray-900 p-4">
<pre class="text-sm text-gray-200"><code>{`{
"package": "package-name",
"type": "endpoint_type",
"data": [...]
}`}</code></pre>
</div>
<h2 class="mt-8 mb-4 text-2xl font-semibold text-gray-100">Error Handling</h2>
<p class="mb-4 text-gray-400">
The API uses standard HTTP status codes:
</p>
<ul class="mb-6 list-disc pl-6 text-gray-400">
<li><strong>200:</strong> Success</li>
<li><strong>404:</strong> Package not found</li>
<li><strong>500:</strong> Internal server error</li>
</ul>
<h2 class="mt-8 mb-4 text-2xl font-semibold text-gray-100">Rate Limiting</h2>
<h2 class="mt-8 mb-4 text-2xl font-semibold text-gray-100">Interactive API Documentation</h2>
<p class="mb-6 text-gray-400">
We may implement rate limiting to ensure fair usage. Please be respectful of our servers
and implement appropriate caching in your applications.
Explore our API endpoints interactively using the documentation below. You can test endpoints directly from this interface.
</p>
<!-- Scalar API Reference iframe -->
<div class="rounded-lg border border-gray-800 bg-gray-900 overflow-hidden">
<iframe
src="https://cdn.jsdelivr.net/npm/@scalar/api-reference/dist/index.html?url=https://pypistats.dev/openapi.yaml"
class="w-full h-[800px] border-0"
title="PyPI Stats API Documentation"
loading="lazy"
></iframe>
</div>
<div class="mt-6 text-sm text-gray-400">
<p>
<strong>Note:</strong> The interactive documentation above uses our OpenAPI specification.
All endpoints are free to use and return JSON data. For programmatic access, use the base URL:
<code class="bg-gray-800 px-2 py-1 rounded">https://pypistats.dev/api</code>
</p>
</div>
</div>
</div>

View File

@@ -1,7 +1,12 @@
<svelte:head>
<title>FAQs - PyPI Stats</title>
<meta name="description" content="Frequently asked questions about PyPI Stats" />
</svelte:head>
<script lang="ts">
import { MetaTags } from 'svelte-meta-tags';
</script>
<MetaTags
title="Frequently Asked Questions - PyPI Stats"
description="Find answers to common questions about PyPI Stats, data accuracy, API usage, and how we collect Python package download statistics."
keywords={["PyPI Stats FAQ", "frequently asked questions", "Python package statistics", "API usage", "data accuracy", "download statistics"]}
/>
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<h1 class="mb-8 text-3xl font-bold text-gray-100">Frequently Asked Questions</h1>

View File

@@ -2,6 +2,7 @@
import type { PageData } from './$types';
import { onMount, onDestroy } from 'svelte';
import LoadingSpinner from '$lib/components/LoadingSpinner.svelte';
import { MetaTags } from 'svelte-meta-tags';
const { data }: { data: PageData } = $props();
let overallCanvas: HTMLCanvasElement | null = $state(null);
@@ -156,10 +157,11 @@
});
</script>
<svelte:head>
<title>{data.packageName} - PyPI Stats</title>
<meta name="description" content="Download statistics for {data.packageName} package" />
</svelte:head>
<MetaTags
title="Python Package Download Statistics - PyPI Stats"
description="View comprehensive download statistics for Python packages from PyPI. Track package popularity, Python version usage, system breakdowns, and download trends."
keywords={["Python package statistics", "PyPI downloads", "package analytics", "download trends", "Python version usage", "system breakdown"]}
/>
<div class="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
<div class="mb-8">

View File

@@ -1,13 +1,16 @@
<script lang="ts">
import type { PageData } from './$types';
import LoadingSpinner from '$lib/components/LoadingSpinner.svelte';
import { MetaTags } from 'svelte-meta-tags';
const { data } = $props<{ data: PageData }>();
let searchTerm = $state(data.searchTerm ?? '');
</script>
<svelte:head>
<title>Search Packages - PyPI Stats</title>
</svelte:head>
<MetaTags
title="Search Python Packages - PyPI Stats"
description="Search for Python packages to view their download statistics, usage trends, and popularity data from PyPI."
keywords={["Python package search", "PyPI search", "package discovery", "Python packages", "download statistics"]}
/>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div class="max-w-2xl mx-auto">