feat: decouple market from shared_lib, add app-owned models

Phase 1-3 of decoupling:
- path_setup.py adds project root to sys.path
- Market-owned models in market/models/ (market, market_place)
- All imports updated: shared.infrastructure, shared.db, shared.browser, etc.
- MarketPlace uses container_type/container_id instead of post_id FK

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
giles
2026-02-11 12:46:32 +00:00
parent 41b4e0fe24
commit 478636f799
51 changed files with 604 additions and 93 deletions

View File

@@ -27,8 +27,8 @@ from models.market import (
ProductAllergen,
)
from suma_browser.app.redis_cacher import clear_cache
from suma_browser.app.csrf import csrf_exempt
from shared.browser.app.redis_cacher import clear_cache
from shared.browser.app.csrf import csrf_exempt
products_api = Blueprint("products_api", __name__, url_prefix="/api/products")

View File

@@ -10,7 +10,7 @@ from quart import (
make_response,
current_app,
)
from config import config
from shared.config import config
from .services.nav import category_context, get_nav
from .services.blacklist.category import is_category_blocked
@@ -21,8 +21,8 @@ from .services import (
_current_url_without_page,
)
from suma_browser.app.redis_cacher import cache_page
from suma_browser.app.utils.htmx import is_htmx_request
from shared.browser.app.redis_cacher import cache_page
from shared.browser.app.utils.htmx import is_htmx_request
def register():
browse_bp = Blueprint("browse", __name__)

View File

@@ -1,7 +1,7 @@
# suma_browser/category_blacklist.py
from __future__ import annotations
from typing import Optional
from config import config
from shared.config import config
def _norm(s: str) -> str:
return (s or "").strip().lower().strip("/")

View File

@@ -1,6 +1,6 @@
from typing import Set, Optional
from ..slugs import canonical_html_slug
from config import config
from shared.config import config
_blocked: Set[str] = set()
_mtime: Optional[float] = None

View File

@@ -1,5 +1,5 @@
import re
from config import config
from shared.config import config
def _norm_title_key(t: str) -> str:
t = (t or "").strip().lower()

View File

@@ -1,7 +1,7 @@
from __future__ import annotations
import os, json
from typing import List, Optional
from config import config
from shared.config import config
from .blacklist.product import is_product_blocked

View File

@@ -4,7 +4,7 @@ from typing import Dict, List, Optional
from sqlalchemy import select, and_
from sqlalchemy.orm import selectinload
from config import config # if unused elsewhere, you can remove this import
from shared.config import config # if unused elsewhere, you can remove this import
# ORM models
from models.market import (

View File

@@ -5,7 +5,7 @@ import re
from typing import Dict, List, Tuple, Optional
from urllib.parse import urlparse, urljoin
from config import config
from shared.config import config
from . import db_backend as cb
from .blacklist.category import is_category_blocked # Reverse map: slug -> label

View File

@@ -6,11 +6,11 @@ from quart import (
g,
request,
)
from config import config
from shared.config import config
from .products import products, products_nocounts
from .blacklist.product_details import is_blacklisted_heading
from utils import host_url
from shared.utils import host_url
from sqlalchemy import select
@@ -163,7 +163,7 @@ def _massage_product(d):
# Re-export from canonical shared location
from shared.http_utils import vary as _vary, current_url_without_page as _current_url_without_page
from shared.infrastructure.http_utils import vary as _vary, current_url_without_page as _current_url_without_page
async def _is_liked(user_id: int | None, slug: str) -> bool:
"""

View File

@@ -1,6 +1,6 @@
import re
from urllib.parse import urljoin, urlparse
from config import config
from shared.config import config
def product_slug_from_href(href: str) -> str:
p = urlparse(href)

View File

@@ -1,4 +1,4 @@
# Re-export from canonical shared location
from shared.cart_identity import CartIdentity, current_cart_identity
from shared.infrastructure.cart_identity import CartIdentity, current_cart_identity
__all__ = ["CartIdentity", "current_cart_identity"]

View File

@@ -5,7 +5,7 @@ from quart import (
)
from suma_browser.app.authz import require_admin
from shared.browser.app.authz import require_admin
def register():
@@ -15,7 +15,7 @@ def register():
@bp.get("/")
@require_admin
async def admin():
from suma_browser.app.utils.htmx import is_htmx_request
from shared.browser.app.utils.htmx import is_htmx_request
# Determine which template to use based on request type
if not is_htmx_request():

View File

@@ -2,10 +2,10 @@ from quart import request
from typing import Iterable, Optional, Union
from suma_browser.app.filters.qs_base import (
from shared.browser.app.filters.qs_base import (
KEEP, _norm, make_filter_set, build_qs,
)
from suma_browser.app.filters.query_types import MarketQuery
from shared.browser.app.filters.query_types import MarketQuery
def decode() -> MarketQuery:

View File

@@ -15,8 +15,8 @@ from ..browse.services.slugs import canonical_html_slug
from ..browse.services.blacklist.product import is_product_blocked
from ..browse.services import db_backend as cb
from ..browse.services import _massage_product
from utils import host_url
from suma_browser.app.redis_cacher import cache_page, clear_cache
from shared.utils import host_url
from shared.browser.app.redis_cacher import cache_page, clear_cache
from ..cart.services import total
from .services.product_operations import toggle_product_like, massage_full_product
@@ -94,7 +94,7 @@ def register():
@bp.get("/")
@cache_page(tag="browse")
async def product_detail(slug: str):
from suma_browser.app.utils.htmx import is_htmx_request
from shared.browser.app.utils.htmx import is_htmx_request
# Determine which template to use based on request type
if not is_htmx_request():
@@ -136,11 +136,11 @@ def register():
)
return html
@bp.get("/admin/")
async def admin(slug: str):
from suma_browser.app.utils.htmx import is_htmx_request
from shared.browser.app.utils.htmx import is_htmx_request
if not is_htmx_request():
# Normal browser request: full page with layout
@@ -152,8 +152,8 @@ def register():
return await make_response(html)
from suma_browser.app.bp.cart.services.identity import current_cart_identity
#from suma_browser.app.bp.cart.routes import view_cart
from bp.cart.services.identity import current_cart_identity
#from bp.cart.routes import view_cart
from models.market import CartItem
from quart import request, url_for
@@ -242,7 +242,7 @@ def register():
)
# normal POST: go to cart page
from shared.urls import cart_url
from shared.infrastructure.urls import cart_url
return redirect(cart_url("/"))

View File

@@ -13,7 +13,7 @@ def massage_full_product(product: Product) -> dict:
Convert a Product ORM model to a dictionary with all fields.
Used for rendering product detail pages.
"""
from suma_browser.app.bp.browse.services import _massage_product
from bp.browse.services import _massage_product
gallery = []
if product.image: