{"id":13795848,"url":"https://github.com/florimondmanca/asgi-sitemaps","last_synced_at":"2025-09-20T18:32:47.336Z","repository":{"id":43937731,"uuid":"268309971","full_name":"florimondmanca/asgi-sitemaps","owner":"florimondmanca","description":"Sitemap generation for Python ASGI web apps","archived":false,"fork":false,"pushed_at":"2024-03-20T16:22:18.000Z","size":53,"stargazers_count":21,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-05-02T05:07:52.150Z","etag":null,"topics":["asgi","async","fastapi","python","sitemap-generator","starlette"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/asgi-sitemaps/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/florimondmanca.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-05-31T15:38:57.000Z","updated_at":"2024-08-03T23:05:56.868Z","dependencies_parsed_at":"2024-08-03T23:05:54.947Z","dependency_job_id":null,"html_url":"https://github.com/florimondmanca/asgi-sitemaps","commit_stats":{"total_commits":25,"total_committers":2,"mean_commits":12.5,"dds":0.12,"last_synced_commit":"009b0f04ef4bca29bcfb8304411b27b9b2bda64e"},"previous_names":["florimondmanca/sitemaps"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/florimondmanca%2Fasgi-sitemaps","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/florimondmanca%2Fasgi-sitemaps/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/florimondmanca%2Fasgi-sitemaps/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/florimondmanca%2Fasgi-sitemaps/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/florimondmanca","download_url":"https://codeload.github.com/florimondmanca/asgi-sitemaps/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":233682456,"owners_count":18713553,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asgi","async","fastapi","python","sitemap-generator","starlette"],"created_at":"2024-08-03T23:01:03.156Z","updated_at":"2025-09-20T18:32:42.007Z","avatar_url":"https://github.com/florimondmanca.png","language":"Python","funding_links":[],"categories":["Async"],"sub_categories":[],"readme":"# asgi-sitemaps\n\n[![Build Status](https://dev.azure.com/florimondmanca/public/_apis/build/status/florimondmanca.asgi-sitemaps?branchName=master)](https://dev.azure.com/florimondmanca/public/_build/latest?definitionId=11\u0026branchName=master)\n[![Coverage](https://codecov.io/gh/florimondmanca/asgi-sitemaps/branch/master/graph/badge.svg)](https://codecov.io/gh/florimondmanca/asgi-sitemaps)\n![Python versions](https://img.shields.io/pypi/pyversions/asgi-sitemaps.svg)\n[![Package version](https://badge.fury.io/py/asgi-sitemaps.svg)](https://pypi.org/project/asgi-sitemaps)\n\n[Sitemap](https://www.sitemaps.org) generation for ASGI applications. Inspired by [Django's sitemap framework](https://docs.djangoproject.com/en/3.0/ref/contrib/sitemaps/).\n\n**Contents**\n\n- [Features](#features)\n- [Installation](#installation)\n- [Quickstart](#quickstart)\n- [How-To](#how-to)\n  - [Sitemap sections](#sitemap-sections)\n  - [Dynamic generation from database queries](#dynamic-generation-from-database-queries)\n  - [Advanced web framework integration](#advanced-web-framework-integration)\n- [API Reference](#api-reference)\n  - [`Sitemap`](#class-sitemap)\n  - [`SitemapApp`](#class-sitemapapp)\n\n## Features\n\n- Build and compose sitemap sections into a single dynamic ASGI endpoint.\n- Supports drawing sitemap items from a variety of sources (static lists, (async) ORM queries, etc).\n- Compatible with any ASGI framework.\n- Fully type annotated.\n- 100% test coverage.\n\n## Installation\n\nInstall with pip:\n\n```shell\n$ pip install 'asgi-sitemaps==1.*'\n```\n\n`asgi-sitemaps` requires Python 3.7+.\n\n## Quickstart\n\nLet's build a static sitemap for a \"Hello, world!\" application. The sitemap will contain a single URL entry for the home `/` endpoint.\n\nHere is the project file structure:\n\n```console\n.\n└── server\n    ├── __init__.py\n    ├── app.py\n    └── sitemap.py\n```\n\nFirst, declare a sitemap section by subclassing `Sitemap`, then wrap it in a `SitemapApp`:\n\n```python\n# server/sitemap.py\nimport asgi_sitemaps\n\nclass Sitemap(asgi_sitemaps.Sitemap):\n    def items(self):\n        return [\"/\"]\n\n    def location(self, item: str):\n        return item\n\n    def changefreq(self, item: str):\n        return \"monthly\"\n\nsitemap = asgi_sitemaps.SitemapApp(Sitemap(), domain=\"example.io\")\n```\n\nNow, register the `sitemap` endpoint as a route onto your ASGI app. For example, if using Starlette:\n\n```python\n# server/app.py\nfrom starlette.applications import Starlette\nfrom starlette.responses import PlainTextResponse\nfrom starlette.routing import Route\nfrom .sitemap import sitemap\n\nasync def home(request):\n    return PlainTextResponse(\"Hello, world!\")\n\nroutes = [\n    Route(\"/\", home),\n    Route(\"/sitemap.xml\", sitemap),\n]\n\napp = Starlette(routes=routes)\n```\n\nServe the app using `$ uvicorn server.app:app`, then request the sitemap:\n\n```bash\ncurl http://localhost:8000/sitemap.xml\n```\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"utf-8\"?\u003e\n\u003curlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\u003e\n  \u003curl\u003e\n    \u003cloc\u003ehttp://example.io/\u003c/loc\u003e\n    \u003cchangefreq\u003emonthly\u003c/changefreq\u003e\n    \u003cpriority\u003e0.5\u003c/priority\u003e\n  \u003c/url\u003e\n\u003c/urlset\u003e\n```\n\nTada!\n\nTo learn more:\n\n- See [How-To](#how-to) for more advanced usage, including splitting the sitemap in multiple sections, and dynamically generating entries from database queries.\n- See the [`Sitemap` API reference](#class-sitemap) for all supported sitemap options.\n\n## How-To\n\n### Sitemap sections\n\nYou can combine multiple sitemap classes into a single sitemap endpoint. This is useful to split the sitemap in multiple sections that may have different `items()` and/or sitemap attributes. Such sections could be static pages, blog posts, recent articles, etc.\n\nTo do so, declare multiple sitemap classes, then pass them as a list to `SitemapApp`:\n\n```python\n# server/sitemap.py\nimport asgi_sitemaps\n\nclass StaticSitemap(asgi_sitemaps.Sitemap):\n    ...\n\nclass BlogSitemap(asgi_sitemaps.Sitemap):\n    ...\n\nsitemap = asgi_sitemaps.SitemapApp([StaticSitemap(), BlogSitemap()], domain=\"example.io\")\n```\n\nEntries from each sitemap will be concatenated when building the final `sitemap.xml`.\n\n### Dynamic generation from database queries\n\n`Sitemap.items()` supports consuming any async iterable. This means you can easily integrate with an async database client or ORM so that `Sitemap.items()` fetches and returns relevant rows for generating your sitemap.\n\nHere's an example using [Databases](https://github.com/encode/databases), assuming you have a `Database` instance in `server/resources.py`:\n\n```python\n# server/sitemap.py\nimport asgi_sitemaps\nfrom .resources import database\n\nclass Sitemap(asgi_sitemaps.Sitemap):\n    async def items(self):\n        query = \"SELECT permalink, updated_at FROM articles;\"\n        return await database.fetch_all(query)\n\n    def location(self, row: dict):\n        return row[\"permalink\"]\n```\n\n### Advanced web framework integration\n\nWhile `asgi-sitemaps` is framework-agnostic, you can use the [`.scope` attribute](#scope) available on `Sitemap` instances to feed the ASGI scope into your framework-specific APIs for inspecting and manipulating request information.\n\nHere is an example with [Starlette](https://www.starlette.io) where we build sitemap of static pages. To decouple from the raw URL paths, pages are referred to by view name. We reverse-lookup their URLs by building a `Request` instance from the ASGI `.scope`, and using `.url_for()`:\n\n```python\n# server/sitemap.py\nimport asgi_sitemaps\nfrom starlette.datastructures import URL\nfrom starlette.requests import Request\n\nclass StaticSitemap(asgi_sitemaps.Sitemap):\n    def items(self):\n        return [\"home\", \"about\", \"blog:home\"]\n\n    def location(self, name: str):\n        request = Request(scope=self.scope)\n        url = request.url_for(name)\n        return URL(url).path\n```\n\nThe corresponding Starlette routing table could look something like this:\n\n```python\n# server/routes.py\nfrom starlette.routing import Mount, Route\nfrom . import views\nfrom .sitemap import sitemap\n\nroutes = [\n    Route(\"/\", views.home, name=\"home\"),\n    Route(\"/about\", views.about, name=\"about\"),\n    Route(\"/blog/\", views.blog_home, name=\"blog:home\"),\n    Route(\"/sitemap.xml\", sitemap),\n]\n```\n\n## API Reference\n\n### _class_ `Sitemap`\n\nRepresents a source of sitemap entries.\n\nYou can specify the type `T` of sitemap items for extra type safety:\n\n```python\nimport asgi_sitemaps\n\nclass MySitemap(asgi_sitemaps.Sitemap[str]):\n    ...\n```\n\n#### _async_ `items`\n\nSignature: `async def () -\u003e Union[Iterable[T], AsyncIterable[T]]`\n\n_(**Required**)_ Return an [iterable](https://docs.python.org/3/glossary.html#term-iterable) or an [asynchronous iterable](https://docs.python.org/3/glossary.html#term-asynchronous-iterable) of items of the same type. Each item will be passed as-is to `.location()`, `.lastmod()`, `.changefreq()`, and `.priority()`.\n\nExamples:\n\n```python\n# Simplest usage: return a list\ndef items(self) -\u003e List[str]:\n    return [\"/\", \"/contact\"]\n\n# Async operations are also supported\nasync def items(self) -\u003e List[dict]:\n    query = \"SELECT permalink, updated_at FROM pages;\"\n    return await database.fetch_all(query)\n\n# Sync and async generators are also supported\nasync def items(self) -\u003e AsyncIterator[dict]:\n    query = \"SELECT permalink, updated_at FROM pages;\"\n    async for row in database.aiter_rows(query):\n        yield row\n```\n\n#### `location`\n\nSignature: `def (item: T) -\u003e str`\n\n_(**Required**)_ Return the absolute path of a sitemap item.\n\n\"Absolute path\" means an URL path without a protocol or domain. For example: `/blog/my-article`. (So `https://mydomain.com/blog/my-article` is not a valid location, nor is `mydomain.com/blog/my-article`.)\n\n#### `lastmod`\n\nSignature: `def (item: T) -\u003e Optional[datetime.datetime]`\n\n_(Optional)_ Return the [date of last modification](https://www.sitemaps.org/protocol.html#lastmoddef) of a sitemap item as a [`datetime`](https://docs.python.org/3/library/datetime.html#datetime.datetime) object, or `None` (the default) for no `lastmod` field.\n\n#### `changefreq`\n\nSignature: `def (item: T) -\u003e Optional[str]`\n\n_(Optional)_ Return the [change frequency](https://www.sitemaps.org/protocol.html#changefreqdef) of a sitemap item.\n\nPossible values are:\n\n- `None` - No `changefreq` field (the default).\n- `\"always\"`\n- `\"hourly\"`\n- `\"daily\"`\n- `\"weekly\"`\n- `\"monthly\"`\n- `\"yearly\"`\n- `\"never\"`\n\n#### `priority`\n\nSignature: `def (item: T) -\u003e float`\n\n_(Optional)_ Return the [priority](https://www.sitemaps.org/protocol.html#prioritydef) of a sitemap item. Must be between 0 and 1. Defaults to `0.5`.\n\n#### `protocol`\n\nType: `str`\n\n_(Optional)_ This attribute defines the protocol used to build URLs of the sitemap.\n\nPossible values are:\n\n- `\"auto\"` - The protocol with which the sitemap was requested (the default).\n- `\"http\"`\n- `\"https\"`\n\n#### `scope`\n\nThis property returns the [ASGI scope](https://asgi.readthedocs.io/en/latest/specs/www.html#connection-scope) of the current HTTP request.\n\n### _class_ `SitemapApp`\n\nAn ASGI application that responds to HTTP requests with the `sitemap.xml` contents of the sitemap.\n\nParameters:\n\n- _(**Required**)_ `sitemaps` - A `Sitemap` object or a list of `Sitemap` objects, used to generate sitemap entries.\n- _(**Required**)_ `domain` - The domain to use when generating sitemap URLs.\n\nExamples:\n\n```python\nsitemap = SitemapApp(Sitemap(), domain=\"mydomain.com\")\nsitemap = SitemapApp([StaticSitemap(), BlogSitemap()], domain=\"mydomain.com\")\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflorimondmanca%2Fasgi-sitemaps","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflorimondmanca%2Fasgi-sitemaps","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflorimondmanca%2Fasgi-sitemaps/lists"}