6 Commits
4 changed files with 44 additions and 15 deletions
+2 -1
View File
@@ -1,11 +1,12 @@
[project] [project]
name = "uvpn-backend" name = "uvpn-backend"
version = "0.2.0" version = "0.3.0"
description = "FastAPI backend with Docker for uvpn.shop" description = "FastAPI backend with Docker for uvpn.shop"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [
"aiofiles>=25.1.0", "aiofiles>=25.1.0",
"async-lru>=2.3.0",
"bs4>=0.0.2", "bs4>=0.0.2",
"fastapi>=0.136.3", "fastapi>=0.136.3",
"httpx>=0.28.1", "httpx>=0.28.1",
+17 -8
View File
@@ -3,12 +3,15 @@ from typing import Optional, Dict, Any, Tuple
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from fastapi import APIRouter, Request from fastapi import APIRouter, Request
from functools import lru_cache
from httpx import AsyncClient from httpx import AsyncClient
from async_lru import alru_cache
from functools import lru_cache
import asyncio
import logging import logging
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
@@ -64,6 +67,7 @@ class IPInfo:
def __init__(self, html: str): def __init__(self, html: str):
self._data = self._parse(html) self._data = self._parse(html)
@lru_cache(maxsize=1000)
def _parse(self, html: str) -> Optional[IPInfoModel]: def _parse(self, html: str) -> Optional[IPInfoModel]:
"""Парсит HTML и возвращает модель""" """Парсит HTML и возвращает модель"""
soup = BeautifulSoup(html, 'html.parser') soup = BeautifulSoup(html, 'html.parser')
@@ -168,7 +172,14 @@ class IPInfo:
router = APIRouter(prefix='/api/v1', tags=['ip']) router = APIRouter(prefix='/api/v1', tags=['ip'])
@lru_cache @alru_cache(maxsize=1000, ttl=86400)
async def get_ip_info(ip: str, user_agent: str):
"""Асинхронная функция для получения информации об IP с кешированием."""
async with AsyncClient(headers={'User-Agent': user_agent}) as client:
response = await client.get(f'https://ipinfo.io/{ip}')
return response.text
@router.get('/ip') @router.get('/ip')
async def get_ip(request: Request): async def get_ip(request: Request):
forwarded = request.headers.get('X-Forwarded-For') forwarded = request.headers.get('X-Forwarded-For')
@@ -180,11 +191,9 @@ async def get_ip(request: Request):
else: else:
client_ip = request.client.host client_ip = request.client.host
async with AsyncClient(headers={'User-Agent': user_agent}) as client: html = await get_ip_info(client_ip, user_agent)
response = await client.get(f'https://ipinfo.io/{client_ip}')
html = response.text result = await asyncio.to_thread(lambda: IPInfo(html).dict())
ipinfo = IPInfo(html) logging.info(result)
logging.info(ipinfo._data)
return ipinfo.dict() return result
+12 -4
View File
@@ -1,9 +1,17 @@
from fastapi import APIRouter, Request from fastapi import APIRouter
import aiofiles import aiofiles
import json
import asyncio
from async_lru import alru_cache
router = APIRouter(prefix='/api/v1', tags=['tariffs']) router = APIRouter(prefix='/api/v1', tags=['tariffs'])
@alru_cache(maxsize=1, ttl=86400)
async def get_tariffs_from_file():
async with aiofiles.open('data/tariffs.json', 'r', encoding='utf-8') as f:
content = await f.read()
return await asyncio.to_thread(json.loads(content))
@router.get('/tariffs') @router.get('/tariffs')
async def get_tariffs(request: Request): async def get_tariffs():
async with aiofiles.open('data/tariffs.json', 'r', encoding='utf-8') as file: return await get_tariffs_from_file()
return await file.read()
Generated
+12 -1
View File
@@ -47,6 +47,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
] ]
[[package]]
name = "async-lru"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e8/1f/989ecfef8e64109a489fff357450cb73fa73a865a92bd8c272170a6922c2/async_lru-2.3.0.tar.gz", hash = "sha256:89bdb258a0140d7313cf8f4031d816a042202faa61d0ab310a0a538baa1c24b6", size = 16332, upload-time = "2026-03-19T01:04:32.413Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl", hash = "sha256:eea27b01841909316f2cc739807acea1c623df2be8c5cfad7583286397bb8315", size = 8403, upload-time = "2026-03-19T01:04:30.883Z" },
]
[[package]] [[package]]
name = "beautifulsoup4" name = "beautifulsoup4"
version = "4.15.0" version = "4.15.0"
@@ -889,10 +898,11 @@ wheels = [
[[package]] [[package]]
name = "uvpn-backend" name = "uvpn-backend"
version = "0.1.0" version = "0.2.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "aiofiles" }, { name = "aiofiles" },
{ name = "async-lru" },
{ name = "bs4" }, { name = "bs4" },
{ name = "fastapi" }, { name = "fastapi" },
{ name = "httpx" }, { name = "httpx" },
@@ -907,6 +917,7 @@ dev = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "aiofiles", specifier = ">=25.1.0" }, { name = "aiofiles", specifier = ">=25.1.0" },
{ name = "async-lru", specifier = ">=2.3.0" },
{ name = "bs4", specifier = ">=0.0.2" }, { name = "bs4", specifier = ">=0.0.2" },
{ name = "fastapi", specifier = ">=0.136.3" }, { name = "fastapi", specifier = ">=0.136.3" },
{ name = "httpx", specifier = ">=0.28.1" }, { name = "httpx", specifier = ">=0.28.1" },