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.
@@ -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,5 @@
1
+ SQLAlchemy==2.0.43
2
+ psycopg2-binary==2.9.11
3
+ python-dotenv==1.1.1
4
+ pydantic==2.11.7
5
+ pydantic-settings==2.10.1
@@ -0,0 +1,2 @@
1
+ dist
2
+ nexora_db
@@ -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 = ["."]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+