mirror of
https://github.com/LukeHagar/pypistats.dev.git
synced 2025-12-06 04:21:09 +00:00
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:
@@ -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
72
pnpm-lock.yaml
generated
@@ -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: {}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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/{package}/chart/{type}</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&chart=line&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/{package}/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/{package}/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/{package}/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/{package}/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/{package}/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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user