Initial commit

This commit is contained in:
Тимур Абайдулин
2025-11-26 07:18:11 +03:00
commit a23c88f5b9
10 changed files with 904 additions and 0 deletions

6
.env.example Normal file
View File

@@ -0,0 +1,6 @@
# Supabase Configuration
# Получите эти данные из вашего проекта Supabase:
# Settings -> API -> Project URL и Project API keys
SUPABASE_URL=your_supabase_project_url
SUPABASE_KEY=your_supabase_anon_key

33
.gitignore vendored Normal file
View File

@@ -0,0 +1,33 @@
# Environment variables
.env
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/
env.bak/
venv.bak/
# Streamlit
.streamlit/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Distribution / packaging
dist/
build/
*.egg-info/

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 [Your Name]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

343
README.md Normal file
View File

@@ -0,0 +1,343 @@
# Supabase Connector
Простой и удобный Python коннектор для работы с [Supabase](https://supabase.com).
## 🎯 О проекте
Этот проект предоставляет удобную обертку над официальным Python клиентом Supabase, упрощая базовые операции с базой данных и Storage.
## ✨ Возможности
- ✅ Простое подключение к Supabase через переменные окружения
- 📊 CRUD операции (Create, Read, Update, Delete)
- 🔍 Фильтрация и лимитирование результатов
- 📦 Работа с Supabase Storage
- 🛡️ Обработка ошибок
- 🎨 Чистый и понятный API
## 📋 Требования
- Python 3.8+
- Аккаунт Supabase (бесплатный или платный)
## 🚀 Установка
### Вариант 1: Клонирование репозитория
```bash
git clone https://github.com/yourusername/supabase-connector.git
cd supabase-connector
pip install -r requirements.txt
```
### Вариант 2: Установка как пакет (в разработке)
```bash
pip install -e .
```
## ⚙️ Настройка
1. Создайте файл `.env` в корне проекта:
```bash
cp .env.example .env
```
2. Заполните данными из вашего Supabase проекта:
```env
SUPABASE_URL=https://your-project-id.supabase.co
SUPABASE_KEY=your_anon_key
```
**Где найти эти данные:**
1. Откройте [Supabase Dashboard](https://app.supabase.com)
2. Выберите ваш проект
3. Перейдите в Settings → API
4. Скопируйте **Project URL** и **anon/public key**
## 📖 Использование
### Базовый пример
```python
from supabase import SupabaseManager
# Инициализация
sb = SupabaseManager()
# Получение данных
users = sb.select(table="users", limit=10)
print(f"Найдено пользователей: {len(users)}")
# Добавление записи
new_user = sb.insert(
table="users",
data={
"name": "Тимур",
"email": "timur@example.com",
"age": 28
}
)
print(f"Создан пользователь с ID: {new_user.get('id')}")
```
### Фильтрация данных
```python
# Получить пользователей с определенным возрастом
users_30 = sb.select(
table="users",
filters={"age": 30}
)
# Получить только имена и email
users = sb.select(
table="users",
columns="name, email",
limit=5
)
```
### Обновление записей
```python
# Обновить возраст пользователя с ID=1
updated = sb.update(
table="users",
data={"age": 31},
filters={"id": 1}
)
```
### Удаление записей
```python
# Удалить пользователя с ID=1
deleted = sb.delete(
table="users",
filters={"id": 1}
)
```
### Работа с Storage
```python
# Загрузить файл
with open("avatar.png", "rb") as f:
file_data = f.read()
sb.upload_file(
bucket="avatars",
file_path="user_123/avatar.png",
file_data=file_data,
content_type="image/png"
)
# Получить публичный URL
url = sb.get_public_url(
bucket="avatars",
file_path="user_123/avatar.png"
)
print(f"Файл доступен по адресу: {url}")
```
### Продвинутые запросы
Для сложных запросов используйте прямой доступ к клиенту:
```python
client = sb.get_client()
# Запрос с операторами сравнения
adults = client.table("users")\
.select("*")\
.gte("age", 18)\
.order("age", desc=True)\
.execute()
# Поиск по паттерну
ivan_users = client.table("users")\
.select("*")\
.ilike("name", "%иван%")\
.execute()
```
## 📁 Структура проекта
```
supabase-connector/
├── supabase.py # Основной класс SupabaseManager
├── __init__.py # Инициализация пакета
├── example.py # Примеры использования
├── requirements.txt # Зависимости
├── setup.py # Настройка пакета
├── .env.example # Шаблон конфигурации
├── .gitignore # Исключения для git
└── README.md # Документация
```
## 🧪 Запуск примеров
В файле `example.py` находятся готовые примеры использования:
```bash
python example.py
```
Раскомментируйте нужные примеры в функции `main()`.
## 📝 API Reference
### SupabaseManager
#### `__init__()`
Инициализирует подключение к Supabase, используя переменные окружения.
#### `select(table, columns="*", filters=None, limit=None)`
Выполняет SELECT запрос.
**Параметры:**
- `table` (str): Название таблицы
- `columns` (str): Колонки для выборки
- `filters` (dict): Словарь с фильтрами `{column: value}`
- `limit` (int): Максимальное количество записей
**Возвращает:** `List[Dict]`
#### `insert(table, data)`
Вставляет новую запись.
**Параметры:**
- `table` (str): Название таблицы
- `data` (dict): Данные для вставки
**Возвращает:** `Dict`
#### `update(table, data, filters)`
Обновляет записи.
**Параметры:**
- `table` (str): Название таблицы
- `data` (dict): Данные для обновления
- `filters` (dict): Фильтры `{column: value}`
**Возвращает:** `List[Dict]`
#### `delete(table, filters)`
Удаляет записи.
**Параметры:**
- `table` (str): Название таблицы
- `filters` (dict): Фильтры `{column: value}`
**Возвращает:** `List[Dict]`
#### `upload_file(bucket, file_path, file_data, content_type=None)`
Загружает файл в Storage.
**Параметры:**
- `bucket` (str): Название bucket
- `file_path` (str): Путь к файлу в bucket
- `file_data` (bytes): Данные файла
- `content_type` (str): MIME тип файла
**Возвращает:** `bool`
#### `get_public_url(bucket, file_path)`
Получает публичный URL файла.
**Параметры:**
- `bucket` (str): Название bucket
- `file_path` (str): Путь к файлу
**Возвращает:** `str`
#### `get_client()`
Возвращает прямой доступ к клиенту Supabase для сложных операций.
**Возвращает:** `Client`
## 🧪 Создание тестовой таблицы
Для тестирования создайте таблицу в Supabase:
```sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
age INTEGER,
created_at TIMESTAMP DEFAULT NOW()
);
-- Тестовые данные
INSERT INTO users (name, email, age) VALUES
('Иван Иванов', 'ivan@example.com', 30),
('Мария Петрова', 'maria@example.com', 25),
('Алексей Сидоров', 'alex@example.com', 35);
```
## 🔐 Безопасность
- ⚠️ Никогда не коммитьте файл `.env` в git
- Используйте **anon key** для клиентских операций
- Настройте Row Level Security (RLS) в Supabase
- Для серверных операций используйте **service_role key** с осторожностью
## 🛠️ Расширение функциональности
Вы можете легко добавить свои методы в класс `SupabaseManager`:
```python
class SupabaseManager:
# ... существующие методы ...
def count_records(self, table: str) -> int:
"""Подсчет количества записей в таблице"""
data = self.select(table=table)
return len(data)
def search_by_text(self, table: str, column: str, search_term: str):
"""Полнотекстовый поиск"""
return self.client.table(table)\
.select("*")\
.ilike(column, f"%{search_term}%")\
.execute().data
```
## 🐛 Решение проблем
### Ошибка подключения
```
ValueError: SUPABASE_URL и SUPABASE_KEY должны быть установлены
```
**Решение:** Проверьте файл `.env` и убедитесь, что указаны правильные значения.
### Ошибка доступа к таблице
```
Ошибка при выполнении SELECT: ...
```
**Решение:**
1. Убедитесь, что таблица существует
2. Проверьте настройки RLS (Row Level Security)
3. Убедитесь, что у вашего ключа есть доступ к таблице
## 📚 Дополнительные ресурсы
- [Официальная документация Supabase](https://supabase.com/docs)
- [Supabase Python Client](https://supabase.com/docs/reference/python/introduction)
- [Row Level Security](https://supabase.com/docs/guides/auth/row-level-security)
## 🤝 Вклад в проект
Приветствуются любые предложения и улучшения! Создавайте Issues и Pull Requests.
## 📄 Лицензия
MIT License - используйте свободно в своих проектах.
## ⭐ Поддержка
Если проект был полезен, поставьте звезду на GitHub!

8
__init__.py Normal file
View File

@@ -0,0 +1,8 @@
"""
Supabase Connector - простой Python коннектор для Supabase
"""
from .supabase import SupabaseManager
__version__ = "1.0.0"
__all__ = ["SupabaseManager"]

240
example.py Normal file
View File

@@ -0,0 +1,240 @@
"""
Примеры использования SupabaseManager
"""
from supabase import SupabaseManager
def example_select():
"""Пример выборки данных из таблицы"""
print("\n=== Пример SELECT ===")
sb = SupabaseManager()
# Получить все записи
all_users = sb.select(table="users")
print(f"Всего пользователей: {len(all_users)}")
# Получить с фильтром
filtered_users = sb.select(
table="users",
filters={"age": 30}
)
print(f"Пользователей с возрастом 30: {len(filtered_users)}")
# Получить с лимитом
limited_users = sb.select(
table="users",
limit=5
)
print(f"Первые 5 пользователей: {len(limited_users)}")
# Выбрать только определенные колонки
names_only = sb.select(
table="users",
columns="id, name"
)
for user in names_only:
print(f" - {user.get('name')} (ID: {user.get('id')})")
def example_insert():
"""Пример добавления новой записи"""
print("\n=== Пример INSERT ===")
sb = SupabaseManager()
new_user = {
"name": "Тимур Иванов",
"email": f"timur_{hash('test')}@example.com",
"age": 28
}
result = sb.insert(table="users", data=new_user)
if result:
print(f"Создан пользователь: {result.get('name')} с ID {result.get('id')}")
else:
print("Ошибка при создании пользователя")
def example_update():
"""Пример обновления записи"""
print("\n=== Пример UPDATE ===")
sb = SupabaseManager()
# Сначала получим ID первого пользователя
users = sb.select(table="users", limit=1)
if users:
user_id = users[0].get('id')
# Обновим возраст
updated = sb.update(
table="users",
data={"age": 31},
filters={"id": user_id}
)
if updated:
print(f"Обновлен пользователь ID {user_id}: {updated[0]}")
else:
print("Нет пользователей для обновления")
def example_delete():
"""Пример удаления записи"""
print("\n=== Пример DELETE ===")
sb = SupabaseManager()
# Получим последнюю запись
users = sb.select(table="users")
if users:
last_user = users[-1]
user_id = last_user.get('id')
print(f"Удаляем пользователя: {last_user.get('name')} (ID: {user_id})")
deleted = sb.delete(
table="users",
filters={"id": user_id}
)
if deleted:
print(f"✅ Пользователь удален")
else:
print("Нет пользователей для удаления")
def example_complex_query():
"""Пример работы с прямым клиентом Supabase для сложных запросов"""
print("\n=== Пример сложных запросов ===")
sb = SupabaseManager()
client = sb.get_client()
# Использование операторов сравнения
adults = client.table("users")\
.select("*")\
.gte("age", 18)\
.execute()
print(f"Взрослых пользователей (18+): {len(adults.data)}")
# Поиск по паттерну (LIKE)
ivan_users = client.table("users")\
.select("*")\
.ilike("name", "%иван%")\
.execute()
print(f"Пользователей с 'иван' в имени: {len(ivan_users.data)}")
# Сортировка
sorted_users = client.table("users")\
.select("*")\
.order("age", desc=True)\
.limit(3)\
.execute()
print("Топ-3 самых старших пользователей:")
for user in sorted_users.data:
print(f" - {user.get('name')}, {user.get('age')} лет")
def example_storage():
"""Пример работы с Storage (загрузка файлов)"""
print("\n=== Пример работы с Storage ===")
sb = SupabaseManager()
# Создадим тестовый текстовый файл
test_content = "Это тестовый файл для демонстрации работы с Supabase Storage"
file_data = test_content.encode('utf-8')
# Загрузим файл
success = sb.upload_file(
bucket="test-bucket",
file_path="examples/test.txt",
file_data=file_data,
content_type="text/plain"
)
if success:
# Получим публичный URL
url = sb.get_public_url(
bucket="test-bucket",
file_path="examples/test.txt"
)
print(f"Файл доступен по URL: {url}")
else:
print("Ошибка при загрузке файла (проверьте, существует ли bucket 'test-bucket')")
def example_batch_operations():
"""Пример пакетных операций"""
print("\n=== Пример пакетных операций ===")
sb = SupabaseManager()
# Добавим несколько пользователей
users_to_add = [
{"name": "Алексей Петров", "email": f"alex_{i}@example.com", "age": 25 + i}
for i in range(3)
]
print(f"Добавляем {len(users_to_add)} пользователей...")
for user_data in users_to_add:
result = sb.insert(table="users", data=user_data)
if result:
print(f"{result.get('name')}")
def example_error_handling():
"""Пример обработки ошибок"""
print("\n=== Пример обработки ошибок ===")
try:
sb = SupabaseManager()
# Попытка запроса к несуществующей таблице
result = sb.select(table="nonexistent_table")
if not result:
print("⚠️ Таблица не существует или запрос вернул пустой результат")
except Exception as e:
print(f"❌ Произошла ошибка: {e}")
def main():
"""Запуск всех примеров"""
print("=" * 60)
print("Примеры использования SupabaseManager")
print("=" * 60)
try:
# Раскомментируйте нужные примеры
example_select()
# example_insert()
# example_update()
# example_delete()
# example_complex_query()
# example_storage()
# example_batch_operations()
# example_error_handling()
except ValueError as e:
print(f"\n❌ Ошибка инициализации: {e}")
print("\n💡 Убедитесь, что вы создали файл .env с переменными:")
print(" SUPABASE_URL=your_url")
print(" SUPABASE_KEY=your_key")
except Exception as e:
print(f"\n❌ Непредвиденная ошибка: {e}")
if __name__ == "__main__":
main()

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
supabase>=2.0.0
python-dotenv>=1.0.0

39
setup.py Normal file
View File

@@ -0,0 +1,39 @@
from setuptools import setup, find_packages
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
setup(
name="supabase-connector",
version="1.0.0",
author="Your Name",
author_email="your.email@example.com",
description="Простой Python коннектор для работы с Supabase",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/yourusername/supabase-connector",
packages=find_packages(),
classifiers=[
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Topic :: Software Development :: Libraries :: Python Modules",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
],
python_requires=">=3.8",
install_requires=[
"supabase>=2.0.0",
"python-dotenv>=1.0.0",
],
extras_require={
"dev": [
"pytest>=7.0.0",
"black>=22.0.0",
"flake8>=4.0.0",
],
},
)

Binary file not shown.

212
supabase.py Normal file
View File

@@ -0,0 +1,212 @@
import os
from typing import Optional, Dict, List, Any
from supabase import create_client, Client
from dotenv import load_dotenv
# Загружаем переменные окружения
load_dotenv()
class SupabaseManager:
"""
Класс для управления подключением и операциями с Supabase
"""
def __init__(self):
"""
Инициализация подключения к Supabase
"""
self.url: str = os.getenv("SUPABASE_URL", "")
self.key: str = os.getenv("SUPABASE_KEY", "")
self.client: Optional[Client] = None
if not self.url or not self.key:
raise ValueError(
"SUPABASE_URL и SUPABASE_KEY должны быть установлены в переменных окружения"
)
self._connect()
def _connect(self) -> None:
"""
Создает подключение к Supabase
"""
try:
self.client = create_client(self.url, self.key)
print("✅ Успешное подключение к Supabase")
except Exception as e:
print(f"❌ Ошибка подключения к Supabase: {e}")
raise
def get_client(self) -> Client:
"""
Возвращает клиент Supabase
Returns:
Client: Клиент Supabase
"""
if not self.client:
raise ConnectionError("Клиент Supabase не инициализирован")
return self.client
# === Методы для работы с данными ===
def select(
self,
table: str,
columns: str = "*",
filters: Optional[Dict[str, Any]] = None,
limit: Optional[int] = None
) -> List[Dict]:
"""
Выполняет SELECT запрос к таблице
Args:
table: Название таблицы
columns: Колонки для выборки (по умолчанию все)
filters: Словарь с фильтрами {column: value}
limit: Лимит количества записей
Returns:
List[Dict]: Список словарей с данными
"""
try:
query = self.client.table(table).select(columns)
if filters:
for column, value in filters.items():
query = query.eq(column, value)
if limit:
query = query.limit(limit)
response = query.execute()
return response.data
except Exception as e:
print(f"❌ Ошибка при выполнении SELECT: {e}")
return []
def insert(self, table: str, data: Dict[str, Any]) -> Dict:
"""
Вставляет запись в таблицу
Args:
table: Название таблицы
data: Словарь с данными для вставки
Returns:
Dict: Вставленная запись
"""
try:
response = self.client.table(table).insert(data).execute()
print(f"✅ Запись успешно добавлена в таблицу {table}")
return response.data[0] if response.data else {}
except Exception as e:
print(f"❌ Ошибка при вставке данных: {e}")
return {}
def update(
self,
table: str,
data: Dict[str, Any],
filters: Dict[str, Any]
) -> List[Dict]:
"""
Обновляет записи в таблице
Args:
table: Название таблицы
data: Словарь с данными для обновления
filters: Словарь с фильтрами {column: value}
Returns:
List[Dict]: Обновленные записи
"""
try:
query = self.client.table(table).update(data)
for column, value in filters.items():
query = query.eq(column, value)
response = query.execute()
print(f"✅ Записи успешно обновлены в таблице {table}")
return response.data
except Exception as e:
print(f"❌ Ошибка при обновлении данных: {e}")
return []
def delete(self, table: str, filters: Dict[str, Any]) -> List[Dict]:
"""
Удаляет записи из таблицы
Args:
table: Название таблицы
filters: Словарь с фильтрами {column: value}
Returns:
List[Dict]: Удаленные записи
"""
try:
query = self.client.table(table).delete()
for column, value in filters.items():
query = query.eq(column, value)
response = query.execute()
print(f"✅ Записи успешно удалены из таблицы {table}")
return response.data
except Exception as e:
print(f"❌ Ошибка при удалении данных: {e}")
return []
# === Методы для работы с Storage ===
def upload_file(
self,
bucket: str,
file_path: str,
file_data: bytes,
content_type: Optional[str] = None
) -> bool:
"""
Загружает файл в Storage
Args:
bucket: Название bucket
file_path: Путь к файлу в bucket
file_data: Данные файла в байтах
content_type: MIME тип файла
Returns:
bool: True если загрузка успешна
"""
try:
options = {"content-type": content_type} if content_type else {}
self.client.storage.from_(bucket).upload(
file_path,
file_data,
file_options=options
)
print(f"✅ Файл {file_path} успешно загружен в bucket {bucket}")
return True
except Exception as e:
print(f"❌ Ошибка при загрузке файла: {e}")
return False
def get_public_url(self, bucket: str, file_path: str) -> str:
"""
Получает публичный URL файла
Args:
bucket: Название bucket
file_path: Путь к файлу в bucket
Returns:
str: Публичный URL
"""
try:
url = self.client.storage.from_(bucket).get_public_url(file_path)
return url
except Exception as e:
print(f"❌ Ошибка при получении URL: {e}")
return ""