Started with db development
This commit is contained in:
@@ -5,6 +5,12 @@ database:
|
|||||||
url: "sqlite:///alpinebits.db" # For local dev, use SQLite. For prod, override with PostgreSQL URL.
|
url: "sqlite:///alpinebits.db" # For local dev, use SQLite. For prod, override with PostgreSQL URL.
|
||||||
# url: "postgresql://user:password@host:port/dbname" # Example for Postgres
|
# url: "postgresql://user:password@host:port/dbname" # Example for Postgres
|
||||||
|
|
||||||
secrets:
|
alpine_bits_auth:
|
||||||
# Example: API keys, tokens, etc. Use annotatedyaml to mark these as secrets.
|
- hotel_id: "123"
|
||||||
# api_key: !secret "my-secret-key"
|
hotel_name: "Frangart Inn"
|
||||||
|
username: "alice"
|
||||||
|
password: !secret ALICE_PASSWORD
|
||||||
|
- hotel_id: "456"
|
||||||
|
hotel_name: "Bemelmans"
|
||||||
|
username: "bob"
|
||||||
|
password: !secret BOB_PASSWORD
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<OTA_ResRetrieveRS xmlns="http://www.opentravel.org/OTA/2003/05" Version="7.000">
|
<OTA_ResRetrieveRS xmlns="http://www.opentravel.org/OTA/2003/05" Version="7.000">
|
||||||
<ReservationsList>
|
<ReservationsList>
|
||||||
<HotelReservation CreateDateTime="2025-09-27T10:06:47.745357+00:00" ResStatus="Requested" RoomStayReservation="true">
|
<HotelReservation CreateDateTime="2025-09-27T15:34:46.762660+00:00" ResStatus="Requested" RoomStayReservation="true">
|
||||||
<UniqueID Type="14" ID="6b34fe24ac2ff811"/>
|
<UniqueID Type="14" ID="6b34fe24ac2ff811"/>
|
||||||
<RoomStays>
|
<RoomStays>
|
||||||
<RoomStay>
|
<RoomStay>
|
||||||
@@ -36,12 +36,9 @@
|
|||||||
<ListItem ListItem="1" Language="it">Landing page comment</ListItem>
|
<ListItem ListItem="1" Language="it">Landing page comment</ListItem>
|
||||||
<Text>Wix form submission</Text>
|
<Text>Wix form submission</Text>
|
||||||
</Comment>
|
</Comment>
|
||||||
<Comment Name="additional info">
|
|
||||||
<Text>utm_Source: ig | utm_Medium: Instagram_Stories | utm_Campaign: Conversions_Hotel_Bemelmans_ITA | utm_Term: Cold_Traffic_Conversions_Hotel_Bemelmans_ITA | utm_Content: Grafik_4_Spätsommer_23.08-07.09_Landingpage_ITA</Text>
|
|
||||||
</Comment>
|
|
||||||
</Comments>
|
</Comments>
|
||||||
<HotelReservationIDs>
|
<HotelReservationIDs>
|
||||||
<HotelReservationID ResID_Type="13" ResID_SourceContext="99tales"/>
|
<HotelReservationID ResID_Type="13" ResID_Value="PAZXh0bgNhZW0BMABhZGlkAasmYBTNE3QBp1jWuJ9zIpfEGRJMP63fMAMI405yvG5EtH-OT0PxSkAbBJaudFHR6cMtkdHu_aem_fopaFtECyVPNW9fmWfEkyA" ResID_SourceContext="99tales"/>
|
||||||
</HotelReservationIDs>
|
</HotelReservationIDs>
|
||||||
<BasicPropertyInfo HotelCode="123" HotelName="Frangart Inn"/>
|
<BasicPropertyInfo HotelCode="123" HotelName="Frangart Inn"/>
|
||||||
</ResGlobalInfo>
|
</ResGlobalInfo>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ dependencies = [
|
|||||||
"slowapi>=0.1.9",
|
"slowapi>=0.1.9",
|
||||||
"sqlalchemy>=2.0.43",
|
"sqlalchemy>=2.0.43",
|
||||||
"uvicorn>=0.37.0",
|
"uvicorn>=0.37.0",
|
||||||
|
"voluptuous>=0.15.2",
|
||||||
"xsdata-pydantic[cli,lxml,soap]>=24.5",
|
"xsdata-pydantic[cli,lxml,soap]>=24.5",
|
||||||
"xsdata[cli,lxml,soap]>=25.7",
|
"xsdata[cli,lxml,soap]>=25.7",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,4 +2,5 @@
|
|||||||
from .main import main
|
from .main import main
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
print("running test main")
|
||||||
main()
|
main()
|
||||||
@@ -1,10 +1,89 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import yaml
|
from pathlib import Path
|
||||||
import annotatedyaml
|
from typing import Any, Dict, List, Optional
|
||||||
|
from annotatedyaml.loader import (
|
||||||
|
HAS_C_LOADER,
|
||||||
|
JSON_TYPE,
|
||||||
|
LoaderType,
|
||||||
|
Secrets,
|
||||||
|
add_constructor,
|
||||||
|
load_yaml as load_annotated_yaml,
|
||||||
|
load_yaml_dict as load_annotated_yaml_dict,
|
||||||
|
parse_yaml as parse_annotated_yaml,
|
||||||
|
secret_yaml as annotated_secret_yaml,
|
||||||
|
)
|
||||||
|
from voluptuous import Schema, Required, All, Length, PREVENT_EXTRA, MultipleInvalid
|
||||||
|
|
||||||
CONFIG_PATH = os.path.join(os.path.dirname(__file__), '../../config/config.yaml')
|
# --- Voluptuous schemas ---
|
||||||
|
database_schema = Schema({
|
||||||
|
Required('url'): str
|
||||||
|
}, extra=PREVENT_EXTRA)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
hotel_auth_schema = Schema({
|
||||||
|
Required("hotel_id"): str,
|
||||||
|
Required("hotel_name"): str,
|
||||||
|
Required("username"): str,
|
||||||
|
Required("password"): str
|
||||||
|
}, extra=PREVENT_EXTRA)
|
||||||
|
|
||||||
|
basic_auth_schema = Schema(
|
||||||
|
All([hotel_auth_schema], Length(min=1))
|
||||||
|
)
|
||||||
|
|
||||||
|
config_schema = Schema({
|
||||||
|
Required('database'): database_schema,
|
||||||
|
Required('alpine_bits_auth'): basic_auth_schema
|
||||||
|
}, extra=PREVENT_EXTRA)
|
||||||
|
|
||||||
|
DEFAULT_CONFIG_FILE = 'config.yaml'
|
||||||
|
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
def __init__(self, config_folder: str | Path = None, config_name: str = DEFAULT_CONFIG_FILE, testing_mode: bool = False):
|
||||||
|
if config_folder is None:
|
||||||
|
config_folder = os.environ.get('ALPINEBITS_CONFIG_DIR')
|
||||||
|
if not config_folder:
|
||||||
|
config_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../config'))
|
||||||
|
if isinstance(config_folder, str):
|
||||||
|
config_folder = Path(config_folder)
|
||||||
|
self.config_folder = config_folder
|
||||||
|
self.config_path = os.path.join(config_folder, config_name)
|
||||||
|
self.secrets = Secrets(config_folder)
|
||||||
|
self.testing_mode = testing_mode
|
||||||
|
self._load_config()
|
||||||
|
|
||||||
|
def _load_config(self):
|
||||||
|
stuff = load_annotated_yaml(self.config_path, secrets=self.secrets)
|
||||||
|
try:
|
||||||
|
validated = config_schema(stuff)
|
||||||
|
except MultipleInvalid as e:
|
||||||
|
raise ValueError(f"Config validation error: {e}")
|
||||||
|
self.database = validated['database']
|
||||||
|
self.basic_auth = validated['alpine_bits_auth']
|
||||||
|
self.config = validated
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
return self.config.get(key, default)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def db_url(self) -> str:
|
||||||
|
return self.database['url']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hotel_id(self) -> str:
|
||||||
|
return self.basic_auth['hotel_id']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hotel_name(self) -> str:
|
||||||
|
return self.basic_auth['hotel_name']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def users(self) -> List[Dict[str, str]]:
|
||||||
|
return self.basic_auth['users']
|
||||||
|
|
||||||
|
# For backward compatibility
|
||||||
def load_config():
|
def load_config():
|
||||||
with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
|
return Config().config
|
||||||
# Use annotatedyaml to load secrets if present
|
|
||||||
return annotatedyaml.load(f)
|
|
||||||
|
|||||||
@@ -46,15 +46,20 @@ class HashedCustomer(Base):
|
|||||||
redacted_at = Column(DateTime)
|
redacted_at = Column(DateTime)
|
||||||
|
|
||||||
|
|
||||||
def get_engine():
|
def get_engine(config=None):
|
||||||
|
db_url = None
|
||||||
|
if config and 'database' in config and 'url' in config['database']:
|
||||||
|
db_url = config['database']['url']
|
||||||
|
if not db_url:
|
||||||
db_url = os.environ.get('DATABASE_URL')
|
db_url = os.environ.get('DATABASE_URL')
|
||||||
if db_url:
|
if not db_url:
|
||||||
|
db_url = 'sqlite:///alpinebits.db'
|
||||||
return create_engine(db_url)
|
return create_engine(db_url)
|
||||||
# Default to local sqlite
|
|
||||||
return create_engine('sqlite:///alpinebits.db')
|
|
||||||
|
|
||||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=get_engine())
|
def get_session_local(config=None):
|
||||||
|
engine = get_engine(config)
|
||||||
|
return sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
def init_db():
|
def init_db(config=None):
|
||||||
engine = get_engine()
|
engine = get_engine(config)
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|||||||
@@ -18,20 +18,38 @@ from .simplified_access import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
# DB and config
|
# DB and config
|
||||||
from .db import Customer as DBCustomer, Reservation as DBReservation, HashedCustomer, SessionLocal, init_db
|
from .db import Customer as DBCustomer, Reservation as DBReservation, HashedCustomer, get_session_local, init_db
|
||||||
from .config_loader import load_config
|
from .config_loader import load_config
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
import json
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
print("🚀 Starting AlpineBits XML generation script...")
|
||||||
# Load config (yaml, annotatedyaml)
|
# Load config (yaml, annotatedyaml)
|
||||||
config = load_config()
|
config = load_config()
|
||||||
|
|
||||||
|
# print config for debugging
|
||||||
|
print("Loaded configuration:")
|
||||||
|
print(json.dumps(config, indent=2))
|
||||||
|
|
||||||
|
# Ensure SQLite DB file exists if using SQLite
|
||||||
|
db_url = config.get('database', {}).get('url', '')
|
||||||
|
if db_url.startswith('sqlite:///'):
|
||||||
|
db_path = db_url.replace('sqlite:///', '')
|
||||||
|
db_path = os.path.abspath(db_path)
|
||||||
|
db_dir = os.path.dirname(db_path)
|
||||||
|
if not os.path.exists(db_dir):
|
||||||
|
os.makedirs(db_dir, exist_ok=True)
|
||||||
|
# The DB file will be created by SQLAlchemy if it doesn't exist, but ensure directory exists
|
||||||
|
|
||||||
# Init DB
|
# Init DB
|
||||||
init_db()
|
init_db(config)
|
||||||
|
|
||||||
|
print("📦 Database initialized/ready.")
|
||||||
|
SessionLocal = get_session_local(config)
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
|
|
||||||
# Load data from JSON file
|
# Load data from JSON file
|
||||||
|
|||||||
2
uv.lock
generated
2
uv.lock
generated
@@ -18,6 +18,7 @@ dependencies = [
|
|||||||
{ name = "slowapi" },
|
{ name = "slowapi" },
|
||||||
{ name = "sqlalchemy" },
|
{ name = "sqlalchemy" },
|
||||||
{ name = "uvicorn" },
|
{ name = "uvicorn" },
|
||||||
|
{ name = "voluptuous" },
|
||||||
{ name = "xsdata", extra = ["cli", "lxml", "soap"] },
|
{ name = "xsdata", extra = ["cli", "lxml", "soap"] },
|
||||||
{ name = "xsdata-pydantic", extra = ["cli", "lxml", "soap"] },
|
{ name = "xsdata-pydantic", extra = ["cli", "lxml", "soap"] },
|
||||||
]
|
]
|
||||||
@@ -35,6 +36,7 @@ requires-dist = [
|
|||||||
{ name = "slowapi", specifier = ">=0.1.9" },
|
{ name = "slowapi", specifier = ">=0.1.9" },
|
||||||
{ name = "sqlalchemy", specifier = ">=2.0.43" },
|
{ name = "sqlalchemy", specifier = ">=2.0.43" },
|
||||||
{ name = "uvicorn", specifier = ">=0.37.0" },
|
{ name = "uvicorn", specifier = ">=0.37.0" },
|
||||||
|
{ name = "voluptuous", specifier = ">=0.15.2" },
|
||||||
{ name = "xsdata", extras = ["cli", "lxml", "soap"], specifier = ">=25.7" },
|
{ name = "xsdata", extras = ["cli", "lxml", "soap"], specifier = ">=25.7" },
|
||||||
{ name = "xsdata-pydantic", extras = ["cli", "lxml", "soap"], specifier = ">=24.5" },
|
{ name = "xsdata-pydantic", extras = ["cli", "lxml", "soap"], specifier = ">=24.5" },
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user