|
| 1 | +# asgi-user-agents |
| 2 | + |
| 3 | +[](https://github.com/hasansezertasan/asgi-user-agents/actions?query=event%3Apush+branch%3Amain+workflow%3ACI) |
| 4 | +[](https://codecov.io/gh/hasansezertasan/asgi-user-agents) |
| 5 | +[](https://pypi.org/project/asgi-user-agents) |
| 6 | +[](https://pypi.org/project/asgi-user-agents) |
| 7 | +[](https://github.com/hasansezertasan/asgi-user-agents/blob/main/LICENSE) |
| 8 | +[](https://github.com/hasansezertasan/asgi-user-agents) |
| 9 | + |
| 10 | +[](https://pepy.tech/project/asgi-user-agents) |
| 11 | +[](https://pepy.tech/project/asgi-user-agents) |
| 12 | +[](https://pepy.tech/project/asgi-user-agents) |
| 13 | + |
| 14 | +[User Agents][python-user-agents] integration for [ASGI](https://asgi.readthedocs.io/en/latest/) applications. Works with Starlette, FastAPI, Quart, Litestar -- or any other web framework supporting ASGI that exposes the ASGI `scope`. |
| 15 | + |
| 16 | +----- |
| 17 | + |
| 18 | +## Table of Contents |
| 19 | + |
| 20 | +- [asgi-user-agents](#asgi-user-agents) |
| 21 | + - [Table of Contents](#table-of-contents) |
| 22 | + - [Installation](#installation) |
| 23 | + - [How does it work?](#how-does-it-work) |
| 24 | + - [Usage](#usage) |
| 25 | + - [API Reference](#api-reference) |
| 26 | + - [`UAMiddleware`](#uamiddleware) |
| 27 | + - [`UADetails`](#uadetails) |
| 28 | + - [`UARequest`](#uarequest) |
| 29 | + - [Development](#development) |
| 30 | + - [Author](#author) |
| 31 | + - [Credits](#credits) |
| 32 | + - [Analysis](#analysis) |
| 33 | + - [License](#license) |
| 34 | + |
| 35 | +## Installation |
| 36 | + |
| 37 | +**NOTE**: This is alpha software. Please be sure to pin your dependencies. |
| 38 | + |
| 39 | +> Latest Release |
| 40 | +
|
| 41 | +```bash |
| 42 | +pip install asgi-user-agents |
| 43 | +``` |
| 44 | + |
| 45 | +> Development Version |
| 46 | +
|
| 47 | +```bash |
| 48 | +pip install git+https://github.com/hasansezertasan/asgi-user-agents.git |
| 49 | +``` |
| 50 | + |
| 51 | +## How does it work? |
| 52 | + |
| 53 | +It simply adds a `ua` attribute to the request scope. This attribute is an instance of the `UADetails` class which abstracts the `UserAgent` class from the `user-agents` package 📄. |
| 54 | + |
| 55 | +## Usage |
| 56 | + |
| 57 | +It's pretty simple. Just add the middleware to your ASGI application and access the `ua` attribute from the request scope. |
| 58 | + |
| 59 | +```python |
| 60 | +from asgi_user_agents import UAMiddleware |
| 61 | +from asgi_user_agents import UARequest as Request |
| 62 | +from fastapi.applications import FastAPI |
| 63 | +from starlette.middleware import Middleware |
| 64 | +from starlette.responses import JSONResponse, Response |
| 65 | + |
| 66 | + |
| 67 | +app = FastAPI(middleware=[Middleware(UAMiddleware)]) |
| 68 | + |
| 69 | + |
| 70 | +@app.get("/") |
| 71 | +async def index(request: Request) -> Response: |
| 72 | + ua = request.scope["ua"] |
| 73 | + data = { |
| 74 | + "ua_string": ua.ua_string, |
| 75 | + "os": ua.os, |
| 76 | + "os.family": ua.os.family, |
| 77 | + "os.version": ua.os.version, |
| 78 | + "os.version_string": ua.os.version_string, |
| 79 | + "browser": ua.browser, |
| 80 | + "browser.family": ua.ua.browser.family, |
| 81 | + "browser.version": ua.ua.browser.version, |
| 82 | + "browser.version_string": ua.ua.browser.version_string, |
| 83 | + "device": ua.device, |
| 84 | + "device.family": ua.device.family, |
| 85 | + "device.brand": ua.device.brand, |
| 86 | + "device.model": ua.device.model, |
| 87 | + "is_provided": ua.is_provided, |
| 88 | + "is_tablet": ua.is_tablet, |
| 89 | + "is_mobile": ua.is_mobile, |
| 90 | + "is_touch_capable": ua.is_touch_capable, |
| 91 | + "is_pc": ua.is_pc, |
| 92 | + "is_bot": ua.is_bot, |
| 93 | + "is_email_client": ua.is_email_client, |
| 94 | + } |
| 95 | + return JSONResponse(data) |
| 96 | + |
| 97 | +``` |
| 98 | + |
| 99 | +## API Reference |
| 100 | + |
| 101 | +### `UAMiddleware` |
| 102 | + |
| 103 | +An ASGI middleware that sets `scope["ua"]` to an instance of [`UADetails`](#uadetails) (`scope` refers to the ASGI scope). |
| 104 | + |
| 105 | +```python |
| 106 | +app = UAMiddleware(app) |
| 107 | +``` |
| 108 | + |
| 109 | +### `UADetails` |
| 110 | + |
| 111 | +A helper that provides shortcuts for accessing [`User-Agent` request header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent). |
| 112 | + |
| 113 | +```python |
| 114 | +ua = UADetails(scope) |
| 115 | +``` |
| 116 | + |
| 117 | +- `ua: UserAgent` - The `UserAgent` instance from the `user-agents` package. |
| 118 | +- `ua_string: str` - The user agent string. |
| 119 | +- `is_provided: bool` - `True` if the user agent string is provided. |
| 120 | +- `os: OperatingSystem` - The operating system details of the user agent. It's a named tuple with the following fields: |
| 121 | + - `family: str` - The family of the operating system. |
| 122 | + - `version: str` - The version of the operating system. |
| 123 | + - `version_string: str` - The version of the operating system as a string. |
| 124 | +- `browser: Browser` - The browser details of the user agent. It's a named tuple with the following fields: |
| 125 | + - `family: str` - The family of the browser. |
| 126 | + - `version: str` - The version of the browser. |
| 127 | + - `version_string: str` - The version of the browser as a string. |
| 128 | +- `device: Device` - The device details of the user agent. It's a named tuple with the following fields: |
| 129 | + - `family: str` - The family of the device. |
| 130 | + - `brand: str` - The brand of the device. |
| 131 | + - `model: str` - The model of the device. |
| 132 | +- `is_tablet: bool` - `True` if the request was made by a tablet. |
| 133 | +- `is_mobile: bool` - `True` if the request was made by a mobile device. |
| 134 | +- `is_touch_capable: bool` - `True` if the request was made by a touch-capable device. |
| 135 | +- `is_pc: bool` - `True` if the request was made by a PC. |
| 136 | +- `is_bot: bool` - `True` if the request was made by a bot. |
| 137 | +- `is_email_client: bool` - `True` if the request was made by an email client. |
| 138 | + |
| 139 | +### `UARequest` |
| 140 | + |
| 141 | +For Starlette-based frameworks, use this instead of the standard `starlette.requests.Request` so that code editors understand that `request.scope["ua"]` contains an `UADetails` instance: |
| 142 | + |
| 143 | +```python |
| 144 | +from asgi_user_agents import UARequest as Request |
| 145 | + |
| 146 | +async def home(request: Request): |
| 147 | + reveal_type(request.scope["ua"]) # Revealed type is 'UADetails' |
| 148 | +``` |
| 149 | + |
| 150 | +## Development |
| 151 | + |
| 152 | +Clone the repository and cd into the project directory: |
| 153 | + |
| 154 | +```bash |
| 155 | +git clone https://github.com/hasansezertasan/asgi-user-agents |
| 156 | +cd asgi-user-agents |
| 157 | +``` |
| 158 | + |
| 159 | +Install hatch, you can follow the instructions [here](https://hatch.pypa.io/latest/install/), or simply run the following command: |
| 160 | + |
| 161 | +```bash |
| 162 | +pipx install hatch |
| 163 | +``` |
| 164 | + |
| 165 | +Initialize the environment and install the dependencies: |
| 166 | + |
| 167 | +```bash |
| 168 | +hatch shell |
| 169 | +``` |
| 170 | + |
| 171 | +Initialize pre-commit hooks by running the following command: |
| 172 | + |
| 173 | +```bash |
| 174 | +pre-commit install |
| 175 | +``` |
| 176 | + |
| 177 | +Make your changes on a new branch and run the tests: |
| 178 | + |
| 179 | +```bash |
| 180 | +hatch test -a |
| 181 | +``` |
| 182 | + |
| 183 | +Make sure that the code is typed, linted, and formatted correctly: |
| 184 | + |
| 185 | +```bash |
| 186 | +hatch run types:all |
| 187 | +``` |
| 188 | + |
| 189 | +Stage your changes and commit them: |
| 190 | + |
| 191 | +```bash |
| 192 | +git add . |
| 193 | +git commit -m "Your message" |
| 194 | +``` |
| 195 | + |
| 196 | +Push your changes to the repository: |
| 197 | + |
| 198 | +```bash |
| 199 | +git push |
| 200 | +``` |
| 201 | + |
| 202 | +Create a pull request and wait for the review 🤓. |
| 203 | + |
| 204 | +## Author |
| 205 | + |
| 206 | +- [Hasan Sezer Taşan](https://www.github.com/hasansezertasan), It's me 👋. |
| 207 | + |
| 208 | +## Credits |
| 209 | + |
| 210 | +- This project wouldn't be possible without the [user-agents][python-user-agents] package 🙏. |
| 211 | +- The project structure is inspired by the [asgi-htmx](https://github.com/florimondmanca/asgi-htmx) 🚀 package and contains some code snippets from it 😅 (even this file). |
| 212 | + |
| 213 | +## Analysis |
| 214 | + |
| 215 | +- [Snyk Python Package Health Analysis](https://snyk.io/advisor/python/asgi-user-agents) |
| 216 | +- [Libraries.io - PyPI](https://libraries.io/pypi/asgi-user-agents) |
| 217 | +- [Safety DB](https://data.safetycli.com/packages/pypi/asgi-user-agents) |
| 218 | +- [PePy Download Stats](https://www.pepy.tech/projects/asgi-user-agents) |
| 219 | +- [PyPI Download Stats](https://pypistats.org/packages/asgi-user-agents) |
| 220 | +- [Pip Trends Download Stats](https://piptrends.com/package/asgi-user-agents) |
| 221 | + |
| 222 | +## License |
| 223 | + |
| 224 | +`asgi-user-agents` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. |
| 225 | + |
| 226 | +<!-- Links --> |
| 227 | +[python-user-agents]: https://github.com/selwin/python-user-agents |
0 commit comments