nexora-db 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nexora_db-0.1.0/PKG-INFO +238 -0
- nexora_db-0.1.0/README.md +225 -0
- nexora_db-0.1.0/nexora_db/__init__.py +0 -0
- nexora_db-0.1.0/nexora_db/configs/__init__.py +0 -0
- nexora_db-0.1.0/nexora_db/configs/database.py +34 -0
- nexora_db-0.1.0/nexora_db/models/__init__.py +0 -0
- nexora_db-0.1.0/nexora_db/models/alerts_model.py +16 -0
- nexora_db-0.1.0/nexora_db/models/devices_model.py +26 -0
- nexora_db-0.1.0/nexora_db/models/user.py +14 -0
- nexora_db-0.1.0/nexora_db/operations/__init__.py +0 -0
- nexora_db-0.1.0/nexora_db/operations/alerts_ops.py +66 -0
- nexora_db-0.1.0/nexora_db/operations/devices_ops.py +131 -0
- nexora_db-0.1.0/nexora_db/operations/users_service.py +53 -0
- nexora_db-0.1.0/nexora_db/schema/__init__.py +0 -0
- nexora_db-0.1.0/nexora_db/schema/validator.py +29 -0
- nexora_db-0.1.0/nexora_db.egg-info/PKG-INFO +238 -0
- nexora_db-0.1.0/nexora_db.egg-info/SOURCES.txt +20 -0
- nexora_db-0.1.0/nexora_db.egg-info/dependency_links.txt +1 -0
- nexora_db-0.1.0/nexora_db.egg-info/requires.txt +5 -0
- nexora_db-0.1.0/nexora_db.egg-info/top_level.txt +2 -0
- nexora_db-0.1.0/pyproject.toml +24 -0
- nexora_db-0.1.0/setup.cfg +4 -0
nexora_db-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nexora_db
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Shared database layer for Nexora monitoring platform
|
|
5
|
+
Author: Derradji
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: SQLAlchemy==2.0.43
|
|
9
|
+
Requires-Dist: psycopg2-binary==2.9.11
|
|
10
|
+
Requires-Dist: python-dotenv==1.1.1
|
|
11
|
+
Requires-Dist: pydantic==2.11.7
|
|
12
|
+
Requires-Dist: pydantic-settings==2.10.1
|
|
13
|
+
|
|
14
|
+
# nexora_db
|
|
15
|
+
|
|
16
|
+
**Shared database layer for the Nexora monitoring platform.**
|
|
17
|
+
|
|
18
|
+
`nexora_db` is an installable Python package that provides a unified, reusable database interface for all Nexora services. It encapsulates SQLAlchemy models, database configuration, and CRUD operations for users, devices, and alerts — keeping your database logic in one place and out of your application code.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Table of Contents
|
|
23
|
+
|
|
24
|
+
- [Features](#features)
|
|
25
|
+
- [Requirements](#requirements)
|
|
26
|
+
- [Installation](#installation)
|
|
27
|
+
- [Configuration](#configuration)
|
|
28
|
+
- [Project Structure](#project-structure)
|
|
29
|
+
- [Usage](#usage)
|
|
30
|
+
- [Initialize the Database](#1-initialize-the-database)
|
|
31
|
+
- [Session Usage](#2-session-usage)
|
|
32
|
+
- [Users](#3-users)
|
|
33
|
+
- [Pydantic Schemas](#4-pydantic-schemas)
|
|
34
|
+
- [Development](#development)
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- 🗄️ **Centralized database layer** — one package, shared across all Nexora microservices
|
|
40
|
+
- 🔌 **PostgreSQL support** via `psycopg2-binary` and SQLAlchemy 2.0
|
|
41
|
+
- 🧱 **SQLAlchemy ORM models** for Users, Devices, and Alerts
|
|
42
|
+
- ⚙️ **Pydantic-based configuration** with `.env` file support
|
|
43
|
+
- 📦 **Installable as a package** via `pip` or `pyproject.toml`
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Requirements
|
|
48
|
+
|
|
49
|
+
- Python >= 3.9
|
|
50
|
+
- PostgreSQL database instance
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
Install directly from source:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install .
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Or install in editable/development mode:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install -e .
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Dependencies
|
|
69
|
+
|
|
70
|
+
| Package | Version |
|
|
71
|
+
|---|---|
|
|
72
|
+
| SQLAlchemy | 2.0.43 |
|
|
73
|
+
| psycopg2-binary | 2.9.11 |
|
|
74
|
+
| python-dotenv | 1.1.1 |
|
|
75
|
+
| pydantic | 2.11.7 |
|
|
76
|
+
| pydantic-settings | 2.10.1 |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Configuration
|
|
81
|
+
|
|
82
|
+
`nexora_db` uses `pydantic-settings` to load configuration from environment variables or a `.env` file.
|
|
83
|
+
|
|
84
|
+
Create a `.env` file in your project root:
|
|
85
|
+
|
|
86
|
+
```env
|
|
87
|
+
DATABASE_URL=postgresql://user:password@localhost:5432/nexora_db
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
> The `DATABASE_URL` environment variable must be set before importing any models or operations.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Project Structure
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
nexora_db/
|
|
98
|
+
├── __init__.py
|
|
99
|
+
│
|
|
100
|
+
├── configs/
|
|
101
|
+
│ ├── __init__.py
|
|
102
|
+
│ └── database.py # Engine, session factory, Base declarative
|
|
103
|
+
│
|
|
104
|
+
├── models/
|
|
105
|
+
│ ├── __init__.py
|
|
106
|
+
│ ├── user.py # User ORM model
|
|
107
|
+
│ ├── devices_model.py # Device ORM model
|
|
108
|
+
│ └── alerts_model.py # Alert ORM model
|
|
109
|
+
│
|
|
110
|
+
└── operations/
|
|
111
|
+
├── __init__.py
|
|
112
|
+
├── users_service.py # User CRUD operations
|
|
113
|
+
├── devices_ops.py # Device CRUD operations
|
|
114
|
+
└── alerts_ops.py # Alert CRUD operations
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Usage
|
|
120
|
+
|
|
121
|
+
### 1. Initialize the Database
|
|
122
|
+
|
|
123
|
+
Before using any models or operations, call `init_database()` once at application startup with your PostgreSQL connection URL, then call `init_db_tables()` to create all tables.
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from nexora_db.configs.database import init_database, init_db_tables
|
|
127
|
+
|
|
128
|
+
init_database("postgresql://user:password@localhost:5432/nexora_db")
|
|
129
|
+
init_db_tables()
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
> `get_db()` and all operations will raise a `RuntimeError` if called before `init_database()`.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
### 2. Session Usage
|
|
137
|
+
|
|
138
|
+
`get_db()` is a generator — use it with `next()` or inside a dependency injection framework (e.g. FastAPI):
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from nexora_db.configs.database import get_db
|
|
142
|
+
|
|
143
|
+
# Manual usage
|
|
144
|
+
db = next(get_db())
|
|
145
|
+
try:
|
|
146
|
+
# ... perform operations ...
|
|
147
|
+
finally:
|
|
148
|
+
db.close()
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
# FastAPI dependency injection
|
|
153
|
+
from fastapi import Depends
|
|
154
|
+
from nexora_db.configs.database import get_db
|
|
155
|
+
|
|
156
|
+
@app.get("/example")
|
|
157
|
+
def example_route(db=Depends(get_db)):
|
|
158
|
+
...
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
### 3. Users
|
|
164
|
+
|
|
165
|
+
`UserOperations` wraps all user-related database logic. Instantiate it after `init_database()` has been called.
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from nexora_db.operations.users_service import UserOperations, UserUtils
|
|
169
|
+
from nexora_db.schema.validator import UserCreate
|
|
170
|
+
|
|
171
|
+
user_ops = UserOperations()
|
|
172
|
+
|
|
173
|
+
# Create a new user
|
|
174
|
+
user = user_ops.create_user(
|
|
175
|
+
email="alice@example.com",
|
|
176
|
+
hashed_password="hashed_secret"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Fetch by email
|
|
180
|
+
user = user_ops.get_user_by_email("alice@example.com")
|
|
181
|
+
|
|
182
|
+
# Fetch all users
|
|
183
|
+
users = user_ops.get_all_users()
|
|
184
|
+
|
|
185
|
+
# Update a user
|
|
186
|
+
updated = user_ops.update_user(user_id=1, data=UserCreate(email="new@example.com", password="new_hash"))
|
|
187
|
+
|
|
188
|
+
# Delete a user
|
|
189
|
+
deleted = user_ops.delete_user(user_id=1)
|
|
190
|
+
|
|
191
|
+
# Extract username from email
|
|
192
|
+
username = UserUtils.get_username_from_email("alice@example.com") # → "alice"
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
### 4. Pydantic Schemas
|
|
198
|
+
|
|
199
|
+
`nexora_db` ships with Pydantic v2 schemas for input validation and API serialization.
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from nexora_db.schema.validator import UserCreate, UserOut, DeviceCreateForm, DeviceUpdateForm
|
|
203
|
+
|
|
204
|
+
# Validate user input
|
|
205
|
+
payload = UserCreate(email="alice@example.com", password="secret123")
|
|
206
|
+
|
|
207
|
+
# Validate device registration
|
|
208
|
+
device_form = DeviceCreateForm(
|
|
209
|
+
hostname="router-01",
|
|
210
|
+
device_type="router",
|
|
211
|
+
ip_address="192.168.1.1",
|
|
212
|
+
mac_address="AA:BB:CC:DD:EE:FF"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Partial device update
|
|
216
|
+
update_form = DeviceUpdateForm(ip_address="10.0.0.5")
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Available schemas:**
|
|
220
|
+
|
|
221
|
+
| Schema | Purpose |
|
|
222
|
+
|---|---|
|
|
223
|
+
| `UserCreate` | Email + password for creating/updating a user |
|
|
224
|
+
| `UserOut` | Serialized user response (id, email, role) |
|
|
225
|
+
| `DeviceCreateForm` | Full device registration payload |
|
|
226
|
+
| `DeviceUpdateForm` | Partial device update (all fields optional) |
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Development
|
|
231
|
+
|
|
232
|
+
Clone the repository and install in editable mode:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
git clone https://github.com/your-org/nexora_db.git
|
|
236
|
+
cd nexora_db
|
|
237
|
+
pip install -e .
|
|
238
|
+
```
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# nexora_db
|
|
2
|
+
|
|
3
|
+
**Shared database layer for the Nexora monitoring platform.**
|
|
4
|
+
|
|
5
|
+
`nexora_db` is an installable Python package that provides a unified, reusable database interface for all Nexora services. It encapsulates SQLAlchemy models, database configuration, and CRUD operations for users, devices, and alerts — keeping your database logic in one place and out of your application code.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Features](#features)
|
|
12
|
+
- [Requirements](#requirements)
|
|
13
|
+
- [Installation](#installation)
|
|
14
|
+
- [Configuration](#configuration)
|
|
15
|
+
- [Project Structure](#project-structure)
|
|
16
|
+
- [Usage](#usage)
|
|
17
|
+
- [Initialize the Database](#1-initialize-the-database)
|
|
18
|
+
- [Session Usage](#2-session-usage)
|
|
19
|
+
- [Users](#3-users)
|
|
20
|
+
- [Pydantic Schemas](#4-pydantic-schemas)
|
|
21
|
+
- [Development](#development)
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
- 🗄️ **Centralized database layer** — one package, shared across all Nexora microservices
|
|
27
|
+
- 🔌 **PostgreSQL support** via `psycopg2-binary` and SQLAlchemy 2.0
|
|
28
|
+
- 🧱 **SQLAlchemy ORM models** for Users, Devices, and Alerts
|
|
29
|
+
- ⚙️ **Pydantic-based configuration** with `.env` file support
|
|
30
|
+
- 📦 **Installable as a package** via `pip` or `pyproject.toml`
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Requirements
|
|
35
|
+
|
|
36
|
+
- Python >= 3.9
|
|
37
|
+
- PostgreSQL database instance
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
Install directly from source:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or install in editable/development mode:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install -e .
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Dependencies
|
|
56
|
+
|
|
57
|
+
| Package | Version |
|
|
58
|
+
|---|---|
|
|
59
|
+
| SQLAlchemy | 2.0.43 |
|
|
60
|
+
| psycopg2-binary | 2.9.11 |
|
|
61
|
+
| python-dotenv | 1.1.1 |
|
|
62
|
+
| pydantic | 2.11.7 |
|
|
63
|
+
| pydantic-settings | 2.10.1 |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Configuration
|
|
68
|
+
|
|
69
|
+
`nexora_db` uses `pydantic-settings` to load configuration from environment variables or a `.env` file.
|
|
70
|
+
|
|
71
|
+
Create a `.env` file in your project root:
|
|
72
|
+
|
|
73
|
+
```env
|
|
74
|
+
DATABASE_URL=postgresql://user:password@localhost:5432/nexora_db
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
> The `DATABASE_URL` environment variable must be set before importing any models or operations.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Project Structure
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
nexora_db/
|
|
85
|
+
├── __init__.py
|
|
86
|
+
│
|
|
87
|
+
├── configs/
|
|
88
|
+
│ ├── __init__.py
|
|
89
|
+
│ └── database.py # Engine, session factory, Base declarative
|
|
90
|
+
│
|
|
91
|
+
├── models/
|
|
92
|
+
│ ├── __init__.py
|
|
93
|
+
│ ├── user.py # User ORM model
|
|
94
|
+
│ ├── devices_model.py # Device ORM model
|
|
95
|
+
│ └── alerts_model.py # Alert ORM model
|
|
96
|
+
│
|
|
97
|
+
└── operations/
|
|
98
|
+
├── __init__.py
|
|
99
|
+
├── users_service.py # User CRUD operations
|
|
100
|
+
├── devices_ops.py # Device CRUD operations
|
|
101
|
+
└── alerts_ops.py # Alert CRUD operations
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Usage
|
|
107
|
+
|
|
108
|
+
### 1. Initialize the Database
|
|
109
|
+
|
|
110
|
+
Before using any models or operations, call `init_database()` once at application startup with your PostgreSQL connection URL, then call `init_db_tables()` to create all tables.
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from nexora_db.configs.database import init_database, init_db_tables
|
|
114
|
+
|
|
115
|
+
init_database("postgresql://user:password@localhost:5432/nexora_db")
|
|
116
|
+
init_db_tables()
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
> `get_db()` and all operations will raise a `RuntimeError` if called before `init_database()`.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### 2. Session Usage
|
|
124
|
+
|
|
125
|
+
`get_db()` is a generator — use it with `next()` or inside a dependency injection framework (e.g. FastAPI):
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from nexora_db.configs.database import get_db
|
|
129
|
+
|
|
130
|
+
# Manual usage
|
|
131
|
+
db = next(get_db())
|
|
132
|
+
try:
|
|
133
|
+
# ... perform operations ...
|
|
134
|
+
finally:
|
|
135
|
+
db.close()
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
# FastAPI dependency injection
|
|
140
|
+
from fastapi import Depends
|
|
141
|
+
from nexora_db.configs.database import get_db
|
|
142
|
+
|
|
143
|
+
@app.get("/example")
|
|
144
|
+
def example_route(db=Depends(get_db)):
|
|
145
|
+
...
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
### 3. Users
|
|
151
|
+
|
|
152
|
+
`UserOperations` wraps all user-related database logic. Instantiate it after `init_database()` has been called.
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from nexora_db.operations.users_service import UserOperations, UserUtils
|
|
156
|
+
from nexora_db.schema.validator import UserCreate
|
|
157
|
+
|
|
158
|
+
user_ops = UserOperations()
|
|
159
|
+
|
|
160
|
+
# Create a new user
|
|
161
|
+
user = user_ops.create_user(
|
|
162
|
+
email="alice@example.com",
|
|
163
|
+
hashed_password="hashed_secret"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Fetch by email
|
|
167
|
+
user = user_ops.get_user_by_email("alice@example.com")
|
|
168
|
+
|
|
169
|
+
# Fetch all users
|
|
170
|
+
users = user_ops.get_all_users()
|
|
171
|
+
|
|
172
|
+
# Update a user
|
|
173
|
+
updated = user_ops.update_user(user_id=1, data=UserCreate(email="new@example.com", password="new_hash"))
|
|
174
|
+
|
|
175
|
+
# Delete a user
|
|
176
|
+
deleted = user_ops.delete_user(user_id=1)
|
|
177
|
+
|
|
178
|
+
# Extract username from email
|
|
179
|
+
username = UserUtils.get_username_from_email("alice@example.com") # → "alice"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
### 4. Pydantic Schemas
|
|
185
|
+
|
|
186
|
+
`nexora_db` ships with Pydantic v2 schemas for input validation and API serialization.
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
from nexora_db.schema.validator import UserCreate, UserOut, DeviceCreateForm, DeviceUpdateForm
|
|
190
|
+
|
|
191
|
+
# Validate user input
|
|
192
|
+
payload = UserCreate(email="alice@example.com", password="secret123")
|
|
193
|
+
|
|
194
|
+
# Validate device registration
|
|
195
|
+
device_form = DeviceCreateForm(
|
|
196
|
+
hostname="router-01",
|
|
197
|
+
device_type="router",
|
|
198
|
+
ip_address="192.168.1.1",
|
|
199
|
+
mac_address="AA:BB:CC:DD:EE:FF"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# Partial device update
|
|
203
|
+
update_form = DeviceUpdateForm(ip_address="10.0.0.5")
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Available schemas:**
|
|
207
|
+
|
|
208
|
+
| Schema | Purpose |
|
|
209
|
+
|---|---|
|
|
210
|
+
| `UserCreate` | Email + password for creating/updating a user |
|
|
211
|
+
| `UserOut` | Serialized user response (id, email, role) |
|
|
212
|
+
| `DeviceCreateForm` | Full device registration payload |
|
|
213
|
+
| `DeviceUpdateForm` | Partial device update (all fields optional) |
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Development
|
|
218
|
+
|
|
219
|
+
Clone the repository and install in editable mode:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
git clone https://github.com/your-org/nexora_db.git
|
|
223
|
+
cd nexora_db
|
|
224
|
+
pip install -e .
|
|
225
|
+
```
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from sqlalchemy import create_engine
|
|
2
|
+
from sqlalchemy.orm import sessionmaker, declarative_base
|
|
3
|
+
|
|
4
|
+
base = declarative_base()
|
|
5
|
+
|
|
6
|
+
engine = None
|
|
7
|
+
SessionLocal = None
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def init_database(database_url: str):
|
|
11
|
+
global engine, SessionLocal
|
|
12
|
+
|
|
13
|
+
engine = create_engine(database_url)
|
|
14
|
+
SessionLocal = sessionmaker(
|
|
15
|
+
autocommit=False,
|
|
16
|
+
autoflush=False,
|
|
17
|
+
bind=engine
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_db():
|
|
22
|
+
if SessionLocal is None:
|
|
23
|
+
raise RuntimeError("Database not initialized. Call init_database() first.")
|
|
24
|
+
db = SessionLocal()
|
|
25
|
+
try:
|
|
26
|
+
yield db
|
|
27
|
+
finally:
|
|
28
|
+
db.close()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def init_db_tables():
|
|
32
|
+
if engine is None:
|
|
33
|
+
raise RuntimeError("Database not initialized.")
|
|
34
|
+
base.metadata.create_all(bind=engine)
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime
|
|
2
|
+
from sqlalchemy.orm import relationship
|
|
3
|
+
from nexora_db.configs.database import base
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Alerts(base):
|
|
8
|
+
__tablename__ = "alerts"
|
|
9
|
+
|
|
10
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
11
|
+
alert_message = Column(String(100), nullable=False)
|
|
12
|
+
alert_level = Column(String(10), nullable=False)
|
|
13
|
+
alert_time = Column(DateTime, default=datetime.utcnow())
|
|
14
|
+
|
|
15
|
+
device_id = Column(Integer, ForeignKey("device.id"), nullable=True)
|
|
16
|
+
device = relationship("Device", back_populates="alerts_device")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from sqlalchemy import Column, Integer, String, DateTime
|
|
2
|
+
from sqlalchemy.orm import relationship
|
|
3
|
+
from nexora_db.configs.database import base
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from sqlalchemy.orm import validates
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Device(base):
|
|
9
|
+
__tablename__ = "device"
|
|
10
|
+
|
|
11
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
12
|
+
hostname = Column(String(20), nullable=False)
|
|
13
|
+
device_type = Column(String(10))
|
|
14
|
+
ip_address = Column(String(15), unique=True, nullable=False)
|
|
15
|
+
mac_address = Column(String(30) , unique=True, nullable=False)
|
|
16
|
+
status = Column(String(10))
|
|
17
|
+
interval = Column(Integer, default=5)
|
|
18
|
+
last_seen = Column(DateTime, default=datetime.utcnow())
|
|
19
|
+
|
|
20
|
+
alerts_device = relationship("Alerts", back_populates="device", cascade="all, delete-orphan")
|
|
21
|
+
|
|
22
|
+
@validates("mac_address")
|
|
23
|
+
def validate_mac(self, key, value):
|
|
24
|
+
if self.mac_address is not None:
|
|
25
|
+
raise ValueError("mac_address cannot be changed once set")
|
|
26
|
+
return value
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from sqlalchemy import Column, Integer, String, DateTime
|
|
2
|
+
from nexora_db.configs.database import base
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class User(base):
|
|
7
|
+
__tablename__ = "users"
|
|
8
|
+
|
|
9
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
10
|
+
email = Column(String, unique=True, index=True, nullable=False)
|
|
11
|
+
hashed_password = Column(String, nullable=False)
|
|
12
|
+
role = Column(String, default="viewer")
|
|
13
|
+
last_seen = Column(DateTime, default=datetime.utcnow())
|
|
14
|
+
|
|
File without changes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from nexora_db.models.alerts_model import Alerts
|
|
2
|
+
from sqlalchemy.exc import SQLAlchemyError
|
|
3
|
+
from nexora_db.operations.devices_ops import DeviceOperations
|
|
4
|
+
from nexora_db.configs.database import get_db
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AlertOperations:
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.session = next(get_db())
|
|
11
|
+
self.device_ops = DeviceOperations()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def create_alert(self, alert_message, alert_level, device_id):
|
|
15
|
+
try:
|
|
16
|
+
alert = Alerts(
|
|
17
|
+
alert_message=alert_message,
|
|
18
|
+
alert_level=alert_level,
|
|
19
|
+
device_id=device_id,
|
|
20
|
+
)
|
|
21
|
+
self.session.add(alert)
|
|
22
|
+
self.session.commit()
|
|
23
|
+
except SQLAlchemyError as e:
|
|
24
|
+
self.session.rollback()
|
|
25
|
+
print(f"[AlertOPS] DB ERROR: {e}")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_all_alerts(self):
|
|
29
|
+
length_of_alerts = len(self.session.query(Alerts).all())
|
|
30
|
+
if not length_of_alerts:
|
|
31
|
+
return False
|
|
32
|
+
return length_of_alerts
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_all_alerts_by_type(self, alert_level):
|
|
36
|
+
alerts_by_type = self.session.query(Alerts).filter(Alerts.alert_level == alert_level).all()
|
|
37
|
+
if not alerts_by_type:
|
|
38
|
+
return False
|
|
39
|
+
return alerts_by_type
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_alerts_by_device_hostname(self, hostname: int):
|
|
43
|
+
alerts = self.device_ops.get_device_by_hostname(hostname).alerts_device
|
|
44
|
+
if not alerts:
|
|
45
|
+
return False
|
|
46
|
+
return alerts
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def delete_alert(self, alert_id: int):
|
|
50
|
+
alert = self.session.query(Alerts).filter(Alerts.id == alert_id).first()
|
|
51
|
+
if not alert:
|
|
52
|
+
return False
|
|
53
|
+
self.session.delete(alert)
|
|
54
|
+
self.session.commit()
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
def get_all_alerts(self):
|
|
58
|
+
return self.session.query(Alerts).all()
|
|
59
|
+
|
|
60
|
+
def delete_alerts_by_device_hostname(self, hostname: str):
|
|
61
|
+
device = self.device_ops.get_device_by_hostname(hostname)
|
|
62
|
+
if not device:
|
|
63
|
+
return False
|
|
64
|
+
self.session.query(Alerts).filter(Alerts.device_id == device.id).delete()
|
|
65
|
+
self.session.commit()
|
|
66
|
+
return True
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from nexora_db.models.devices_model import Device
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from nexora_db.configs.database import get_db
|
|
4
|
+
|
|
5
|
+
class DeviceOperations:
|
|
6
|
+
def __init__(self):
|
|
7
|
+
self.session = next(get_db())
|
|
8
|
+
|
|
9
|
+
def create_device(self, hostname, device_type, ip_address, mac_address, status="START", interval=None):
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
device = self.session.query(Device).filter_by(mac_address=mac_address).first()
|
|
13
|
+
|
|
14
|
+
if device:
|
|
15
|
+
if device.status != status:
|
|
16
|
+
print(f"[STATUS CHANGE] {device.hostname}: {device.status} -> {status}")
|
|
17
|
+
device.status = status
|
|
18
|
+
device.last_seen = datetime.utcnow()
|
|
19
|
+
self.session.commit()
|
|
20
|
+
self.session.refresh(device)
|
|
21
|
+
return device
|
|
22
|
+
|
|
23
|
+
else:
|
|
24
|
+
device = Device(
|
|
25
|
+
hostname=hostname,
|
|
26
|
+
device_type=device_type,
|
|
27
|
+
ip_address=ip_address,
|
|
28
|
+
mac_address=mac_address,
|
|
29
|
+
status=status,
|
|
30
|
+
interval=interval,
|
|
31
|
+
last_seen=datetime.utcnow()
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
self.session.add(device)
|
|
35
|
+
self.session.commit()
|
|
36
|
+
self.session.refresh(device)
|
|
37
|
+
|
|
38
|
+
return device
|
|
39
|
+
|
|
40
|
+
except Exception as e:
|
|
41
|
+
self.session.rollback()
|
|
42
|
+
print("DEVICE OPS ERROR:", e)
|
|
43
|
+
|
|
44
|
+
finally:
|
|
45
|
+
self.session.close()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_device_by_hostname(self, hostname):
|
|
49
|
+
return self.session.query(Device)\
|
|
50
|
+
.filter(Device.hostname == hostname)\
|
|
51
|
+
.first()
|
|
52
|
+
|
|
53
|
+
def get_device_by_mac_address(self, mac_address):
|
|
54
|
+
return self.session.query(Device).filter(Device.mac_address == mac_address).first()
|
|
55
|
+
|
|
56
|
+
def get_device_by_ip_address(self, ip_address):
|
|
57
|
+
return self.session.query(Device).filter(Device.ip_address == ip_address).first()
|
|
58
|
+
|
|
59
|
+
def get_devices_by_status(self, status):
|
|
60
|
+
return self.session.query(Device).filter(Device.status == status).all()
|
|
61
|
+
|
|
62
|
+
def update_last_seen(self, hostname):
|
|
63
|
+
device = self.get_device_by_hostname(hostname)
|
|
64
|
+
if device:
|
|
65
|
+
device.last_seen = datetime.utcnow()
|
|
66
|
+
self.session.commit()
|
|
67
|
+
return device
|
|
68
|
+
device.last_seen = datetime.utcnow()
|
|
69
|
+
self.session.commit()
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def check_device_status(self, hostname: str, status: str):
|
|
74
|
+
if not hostname or not status:
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
device = self.get_device_by_hostname(hostname)
|
|
78
|
+
if not device:
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
if device.status == status:
|
|
82
|
+
self.update_last_seen(hostname) ; device.status = status ; self.session.commit() ; self.session.refresh(device)
|
|
83
|
+
return True
|
|
84
|
+
else:
|
|
85
|
+
self.update_last_seen(hostname)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def delete_device(self, device_mac_address: str):
|
|
89
|
+
device = self.get_device_by_mac_address(device_mac_address)
|
|
90
|
+
if not device:
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
self.session.delete(device)
|
|
94
|
+
self.session.commit()
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_all_devices(self):
|
|
99
|
+
return self.session.query(Device).all()
|
|
100
|
+
|
|
101
|
+
def get_device_by_ip(self, ip_address: str):
|
|
102
|
+
return self.session.query(Device).filter(Device.ip_address == ip_address).first()
|
|
103
|
+
|
|
104
|
+
def get_device_by_mac(self, mac_address: str):
|
|
105
|
+
return self.session.query(Device).filter(Device.mac_address == mac_address).first()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def update_device(self, mac_address: str, data: dict):
|
|
109
|
+
if not mac_address or not data:
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
device = self.session.query(Device).filter(Device.mac_address == mac_address).first()
|
|
114
|
+
if not device:
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
device.hostname = data.get("hostname", device.hostname)
|
|
118
|
+
device.device_type = data.get("device_type", device.device_type)
|
|
119
|
+
device.ip_address = data.get("ip_address", device.ip_address)
|
|
120
|
+
|
|
121
|
+
self.session.commit()
|
|
122
|
+
self.session.refresh(device)
|
|
123
|
+
|
|
124
|
+
return device
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
self.session.rollback()
|
|
128
|
+
return str(e)
|
|
129
|
+
|
|
130
|
+
finally:
|
|
131
|
+
self.session.close()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from nexora_db.models.user import User
|
|
2
|
+
from nexora_db.schema.validator import UserCreate
|
|
3
|
+
from nexora_db.configs.database import get_db
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class UserOperations:
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.db = next(get_db())
|
|
11
|
+
|
|
12
|
+
def create_user(self, email: str, hashed_password: str):
|
|
13
|
+
user = User(email=email, hashed_password=hashed_password)
|
|
14
|
+
self.db.add(user)
|
|
15
|
+
self.db.commit()
|
|
16
|
+
self.db.refresh(user)
|
|
17
|
+
return user
|
|
18
|
+
|
|
19
|
+
def get_user_by_email(self, email: str):
|
|
20
|
+
return self.db.query(User).filter(User.email == email).first()
|
|
21
|
+
|
|
22
|
+
def get_all_users(self):
|
|
23
|
+
return self.db.query(User).all()
|
|
24
|
+
|
|
25
|
+
def delete_user(self, user_id: int):
|
|
26
|
+
user = self.db.query(User).filter(User.id == user_id).first()
|
|
27
|
+
if not user:
|
|
28
|
+
return None
|
|
29
|
+
self.db.delete(user)
|
|
30
|
+
self.db.commit()
|
|
31
|
+
return user
|
|
32
|
+
|
|
33
|
+
def update_user(self, user_id: int, data: UserCreate):
|
|
34
|
+
user = self.db.query(User).filter(User.id == user_id).first()
|
|
35
|
+
if not user:
|
|
36
|
+
return None
|
|
37
|
+
user.email = data.email
|
|
38
|
+
user.hashed_password = data.password
|
|
39
|
+
self.db.commit()
|
|
40
|
+
self.db.refresh(user)
|
|
41
|
+
return user
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class UserUtils:
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def get_username_from_email(email):
|
|
50
|
+
if "@" in email:
|
|
51
|
+
return email.split("@")[0]
|
|
52
|
+
return email
|
|
53
|
+
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from pydantic import BaseModel, EmailStr, Field
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class UserCreate(BaseModel):
|
|
6
|
+
email: EmailStr
|
|
7
|
+
password: str
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class UserOut(BaseModel):
|
|
11
|
+
id: int
|
|
12
|
+
email: EmailStr
|
|
13
|
+
role: str
|
|
14
|
+
|
|
15
|
+
class Config:
|
|
16
|
+
orm_mode = True
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DeviceCreateForm(BaseModel):
|
|
20
|
+
hostname: str = Field(..., min_length=2, max_length=50)
|
|
21
|
+
device_type: str = Field(..., min_length=2, max_length=30)
|
|
22
|
+
ip_address: str = Field(..., min_length=7, max_length=15)
|
|
23
|
+
mac_address: str = Field(..., min_length=17, max_length=17)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DeviceUpdateForm(BaseModel):
|
|
27
|
+
hostname: Optional[str] = Field(None, min_length=2, max_length=50)
|
|
28
|
+
device_type: Optional[str] = Field(None, min_length=2, max_length=30)
|
|
29
|
+
ip_address: Optional[str] = Field(None, pattern=r"^\d{1,3}(\.\d{1,3}){3}$")
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nexora_db
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Shared database layer for Nexora monitoring platform
|
|
5
|
+
Author: Derradji
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: SQLAlchemy==2.0.43
|
|
9
|
+
Requires-Dist: psycopg2-binary==2.9.11
|
|
10
|
+
Requires-Dist: python-dotenv==1.1.1
|
|
11
|
+
Requires-Dist: pydantic==2.11.7
|
|
12
|
+
Requires-Dist: pydantic-settings==2.10.1
|
|
13
|
+
|
|
14
|
+
# nexora_db
|
|
15
|
+
|
|
16
|
+
**Shared database layer for the Nexora monitoring platform.**
|
|
17
|
+
|
|
18
|
+
`nexora_db` is an installable Python package that provides a unified, reusable database interface for all Nexora services. It encapsulates SQLAlchemy models, database configuration, and CRUD operations for users, devices, and alerts — keeping your database logic in one place and out of your application code.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Table of Contents
|
|
23
|
+
|
|
24
|
+
- [Features](#features)
|
|
25
|
+
- [Requirements](#requirements)
|
|
26
|
+
- [Installation](#installation)
|
|
27
|
+
- [Configuration](#configuration)
|
|
28
|
+
- [Project Structure](#project-structure)
|
|
29
|
+
- [Usage](#usage)
|
|
30
|
+
- [Initialize the Database](#1-initialize-the-database)
|
|
31
|
+
- [Session Usage](#2-session-usage)
|
|
32
|
+
- [Users](#3-users)
|
|
33
|
+
- [Pydantic Schemas](#4-pydantic-schemas)
|
|
34
|
+
- [Development](#development)
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- 🗄️ **Centralized database layer** — one package, shared across all Nexora microservices
|
|
40
|
+
- 🔌 **PostgreSQL support** via `psycopg2-binary` and SQLAlchemy 2.0
|
|
41
|
+
- 🧱 **SQLAlchemy ORM models** for Users, Devices, and Alerts
|
|
42
|
+
- ⚙️ **Pydantic-based configuration** with `.env` file support
|
|
43
|
+
- 📦 **Installable as a package** via `pip` or `pyproject.toml`
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Requirements
|
|
48
|
+
|
|
49
|
+
- Python >= 3.9
|
|
50
|
+
- PostgreSQL database instance
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
Install directly from source:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install .
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Or install in editable/development mode:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install -e .
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Dependencies
|
|
69
|
+
|
|
70
|
+
| Package | Version |
|
|
71
|
+
|---|---|
|
|
72
|
+
| SQLAlchemy | 2.0.43 |
|
|
73
|
+
| psycopg2-binary | 2.9.11 |
|
|
74
|
+
| python-dotenv | 1.1.1 |
|
|
75
|
+
| pydantic | 2.11.7 |
|
|
76
|
+
| pydantic-settings | 2.10.1 |
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Configuration
|
|
81
|
+
|
|
82
|
+
`nexora_db` uses `pydantic-settings` to load configuration from environment variables or a `.env` file.
|
|
83
|
+
|
|
84
|
+
Create a `.env` file in your project root:
|
|
85
|
+
|
|
86
|
+
```env
|
|
87
|
+
DATABASE_URL=postgresql://user:password@localhost:5432/nexora_db
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
> The `DATABASE_URL` environment variable must be set before importing any models or operations.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Project Structure
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
nexora_db/
|
|
98
|
+
├── __init__.py
|
|
99
|
+
│
|
|
100
|
+
├── configs/
|
|
101
|
+
│ ├── __init__.py
|
|
102
|
+
│ └── database.py # Engine, session factory, Base declarative
|
|
103
|
+
│
|
|
104
|
+
├── models/
|
|
105
|
+
│ ├── __init__.py
|
|
106
|
+
│ ├── user.py # User ORM model
|
|
107
|
+
│ ├── devices_model.py # Device ORM model
|
|
108
|
+
│ └── alerts_model.py # Alert ORM model
|
|
109
|
+
│
|
|
110
|
+
└── operations/
|
|
111
|
+
├── __init__.py
|
|
112
|
+
├── users_service.py # User CRUD operations
|
|
113
|
+
├── devices_ops.py # Device CRUD operations
|
|
114
|
+
└── alerts_ops.py # Alert CRUD operations
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Usage
|
|
120
|
+
|
|
121
|
+
### 1. Initialize the Database
|
|
122
|
+
|
|
123
|
+
Before using any models or operations, call `init_database()` once at application startup with your PostgreSQL connection URL, then call `init_db_tables()` to create all tables.
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from nexora_db.configs.database import init_database, init_db_tables
|
|
127
|
+
|
|
128
|
+
init_database("postgresql://user:password@localhost:5432/nexora_db")
|
|
129
|
+
init_db_tables()
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
> `get_db()` and all operations will raise a `RuntimeError` if called before `init_database()`.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
### 2. Session Usage
|
|
137
|
+
|
|
138
|
+
`get_db()` is a generator — use it with `next()` or inside a dependency injection framework (e.g. FastAPI):
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from nexora_db.configs.database import get_db
|
|
142
|
+
|
|
143
|
+
# Manual usage
|
|
144
|
+
db = next(get_db())
|
|
145
|
+
try:
|
|
146
|
+
# ... perform operations ...
|
|
147
|
+
finally:
|
|
148
|
+
db.close()
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
# FastAPI dependency injection
|
|
153
|
+
from fastapi import Depends
|
|
154
|
+
from nexora_db.configs.database import get_db
|
|
155
|
+
|
|
156
|
+
@app.get("/example")
|
|
157
|
+
def example_route(db=Depends(get_db)):
|
|
158
|
+
...
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
### 3. Users
|
|
164
|
+
|
|
165
|
+
`UserOperations` wraps all user-related database logic. Instantiate it after `init_database()` has been called.
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from nexora_db.operations.users_service import UserOperations, UserUtils
|
|
169
|
+
from nexora_db.schema.validator import UserCreate
|
|
170
|
+
|
|
171
|
+
user_ops = UserOperations()
|
|
172
|
+
|
|
173
|
+
# Create a new user
|
|
174
|
+
user = user_ops.create_user(
|
|
175
|
+
email="alice@example.com",
|
|
176
|
+
hashed_password="hashed_secret"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Fetch by email
|
|
180
|
+
user = user_ops.get_user_by_email("alice@example.com")
|
|
181
|
+
|
|
182
|
+
# Fetch all users
|
|
183
|
+
users = user_ops.get_all_users()
|
|
184
|
+
|
|
185
|
+
# Update a user
|
|
186
|
+
updated = user_ops.update_user(user_id=1, data=UserCreate(email="new@example.com", password="new_hash"))
|
|
187
|
+
|
|
188
|
+
# Delete a user
|
|
189
|
+
deleted = user_ops.delete_user(user_id=1)
|
|
190
|
+
|
|
191
|
+
# Extract username from email
|
|
192
|
+
username = UserUtils.get_username_from_email("alice@example.com") # → "alice"
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
### 4. Pydantic Schemas
|
|
198
|
+
|
|
199
|
+
`nexora_db` ships with Pydantic v2 schemas for input validation and API serialization.
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from nexora_db.schema.validator import UserCreate, UserOut, DeviceCreateForm, DeviceUpdateForm
|
|
203
|
+
|
|
204
|
+
# Validate user input
|
|
205
|
+
payload = UserCreate(email="alice@example.com", password="secret123")
|
|
206
|
+
|
|
207
|
+
# Validate device registration
|
|
208
|
+
device_form = DeviceCreateForm(
|
|
209
|
+
hostname="router-01",
|
|
210
|
+
device_type="router",
|
|
211
|
+
ip_address="192.168.1.1",
|
|
212
|
+
mac_address="AA:BB:CC:DD:EE:FF"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Partial device update
|
|
216
|
+
update_form = DeviceUpdateForm(ip_address="10.0.0.5")
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Available schemas:**
|
|
220
|
+
|
|
221
|
+
| Schema | Purpose |
|
|
222
|
+
|---|---|
|
|
223
|
+
| `UserCreate` | Email + password for creating/updating a user |
|
|
224
|
+
| `UserOut` | Serialized user response (id, email, role) |
|
|
225
|
+
| `DeviceCreateForm` | Full device registration payload |
|
|
226
|
+
| `DeviceUpdateForm` | Partial device update (all fields optional) |
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Development
|
|
231
|
+
|
|
232
|
+
Clone the repository and install in editable mode:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
git clone https://github.com/your-org/nexora_db.git
|
|
236
|
+
cd nexora_db
|
|
237
|
+
pip install -e .
|
|
238
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
nexora_db/__init__.py
|
|
4
|
+
nexora_db.egg-info/PKG-INFO
|
|
5
|
+
nexora_db.egg-info/SOURCES.txt
|
|
6
|
+
nexora_db.egg-info/dependency_links.txt
|
|
7
|
+
nexora_db.egg-info/requires.txt
|
|
8
|
+
nexora_db.egg-info/top_level.txt
|
|
9
|
+
nexora_db/configs/__init__.py
|
|
10
|
+
nexora_db/configs/database.py
|
|
11
|
+
nexora_db/models/__init__.py
|
|
12
|
+
nexora_db/models/alerts_model.py
|
|
13
|
+
nexora_db/models/devices_model.py
|
|
14
|
+
nexora_db/models/user.py
|
|
15
|
+
nexora_db/operations/__init__.py
|
|
16
|
+
nexora_db/operations/alerts_ops.py
|
|
17
|
+
nexora_db/operations/devices_ops.py
|
|
18
|
+
nexora_db/operations/users_service.py
|
|
19
|
+
nexora_db/schema/__init__.py
|
|
20
|
+
nexora_db/schema/validator.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "nexora_db"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Shared database layer for Nexora monitoring platform"
|
|
9
|
+
authors = [
|
|
10
|
+
{name = "Derradji"}
|
|
11
|
+
]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.9"
|
|
14
|
+
|
|
15
|
+
dependencies = [
|
|
16
|
+
"SQLAlchemy==2.0.43",
|
|
17
|
+
"psycopg2-binary==2.9.11",
|
|
18
|
+
"python-dotenv==1.1.1",
|
|
19
|
+
"pydantic==2.11.7",
|
|
20
|
+
"pydantic-settings==2.10.1"
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["."]
|