Initial commit
This commit is contained in:
6
.env.example
Normal file
6
.env.example
Normal 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
33
.gitignore
vendored
Normal 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
21
LICENSE
Normal 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
343
README.md
Normal 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
8
__init__.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
"""
|
||||||
|
Supabase Connector - простой Python коннектор для Supabase
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .supabase import SupabaseManager
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__all__ = ["SupabaseManager"]
|
||||||
240
example.py
Normal file
240
example.py
Normal 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
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
supabase>=2.0.0
|
||||||
|
python-dotenv>=1.0.0
|
||||||
39
setup.py
Normal file
39
setup.py
Normal 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",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
BIN
streamlit_supabase_project.tar.gz
Normal file
BIN
streamlit_supabase_project.tar.gz
Normal file
Binary file not shown.
212
supabase.py
Normal file
212
supabase.py
Normal 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 ""
|
||||||
Reference in New Issue
Block a user