Compare commits

...

2 Commits

Author SHA1 Message Date
pacnpal
8dd5d88906 Last with the old frontend 2025-08-28 11:38:22 -04:00
pacnpal
c4702559fb Last with the old frontend 2025-08-28 11:37:24 -04:00
11 changed files with 3434 additions and 6 deletions

2
backend/.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# SCM syntax highlighting & preventing 3-way merges
pixi.lock merge=binary linguist-language=YAML linguist-generated=true

3
backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
# pixi environments
.pixi/*
!.pixi/config.toml

View File

@@ -0,0 +1,362 @@
"""
Parks Company API views for ThrillWiki API v1.
This module implements comprehensive Company endpoints for the Parks domain,
handling companies with OPERATOR and PROPERTY_OWNER roles.
Endpoints:
- List / Create: GET /parks/companies/ POST /parks/companies/
- Retrieve / Update / Delete: GET /parks/companies/{pk}/ PATCH/PUT/DELETE
- Search: GET /parks/companies/search/?q=...
"""
from typing import Any
from rest_framework import status, permissions
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
from rest_framework.exceptions import NotFound
from drf_spectacular.utils import extend_schema, OpenApiParameter
from drf_spectacular.types import OpenApiTypes
# Import serializers
from apps.api.v1.serializers.companies import (
CompanyDetailOutputSerializer,
CompanyCreateInputSerializer,
CompanyUpdateInputSerializer,
)
# Attempt to import model-level helpers; fall back gracefully if not present.
try:
from apps.parks.models import Company as ParkCompany # type: ignore
MODELS_AVAILABLE = True
except Exception:
ParkCompany = None # type: ignore
MODELS_AVAILABLE = False
class StandardResultsSetPagination(PageNumberPagination):
page_size = 20
page_size_query_param = "page_size"
max_page_size = 1000
# --- Company list & create -------------------------------------------------
class ParkCompanyListCreateAPIView(APIView):
"""
Parks Company endpoints for OPERATOR and PROPERTY_OWNER companies.
"""
permission_classes = [permissions.AllowAny]
@extend_schema(
summary="List park companies (operators/property owners)",
description=(
"List companies with OPERATOR and PROPERTY_OWNER roles "
"with filtering and pagination."
),
parameters=[
OpenApiParameter(
name="page", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT
),
OpenApiParameter(
name="page_size", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT
),
OpenApiParameter(
name="search", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR
),
OpenApiParameter(
name="roles",
location=OpenApiParameter.QUERY,
type=OpenApiTypes.STR,
description=(
"Filter by roles: OPERATOR, PROPERTY_OWNER (comma-separated)"
),
),
],
responses={200: CompanyDetailOutputSerializer(many=True)},
tags=["Parks", "Companies"],
)
def get(self, request: Request) -> Response:
"""List park companies with filtering and pagination."""
if not MODELS_AVAILABLE:
return Response(
{
"detail": (
"Park company listing is not available because domain models "
"are not imported. Implement apps.parks.models.Company "
"to enable listing."
)
},
status=status.HTTP_501_NOT_IMPLEMENTED,
)
# Filter to only park-related roles
qs = ParkCompany.objects.filter(
roles__overlap=["OPERATOR", "PROPERTY_OWNER"]
).distinct() # type: ignore
# Basic filters
q = request.query_params.get("search")
if q:
qs = qs.filter(name__icontains=q)
roles = request.query_params.get("roles")
if roles:
role_list = [role.strip().upper() for role in roles.split(",")]
# Filter to companies that have any of the specified roles
valid_roles = [r for r in role_list if r in ["OPERATOR", "PROPERTY_OWNER"]]
if valid_roles:
qs = qs.filter(roles__overlap=valid_roles)
paginator = StandardResultsSetPagination()
page = paginator.paginate_queryset(qs, request)
serializer = CompanyDetailOutputSerializer(
page, many=True, context={"request": request}
)
return paginator.get_paginated_response(serializer.data)
@extend_schema(
summary="Create a new park company",
description="Create a new company with OPERATOR and/or PROPERTY_OWNER roles.",
request=CompanyCreateInputSerializer,
responses={201: CompanyDetailOutputSerializer()},
tags=["Parks", "Companies"],
)
def post(self, request: Request) -> Response:
"""Create a new park company."""
if not MODELS_AVAILABLE:
return Response(
{
"detail": (
"Park company creation is not available because domain models "
"are not imported. Implement apps.parks.models.Company "
"and necessary create logic."
)
},
status=status.HTTP_501_NOT_IMPLEMENTED,
)
serializer_in = CompanyCreateInputSerializer(data=request.data)
serializer_in.is_valid(raise_exception=True)
validated = serializer_in.validated_data
# Validate that roles are appropriate for parks domain
roles = validated.get("roles", [])
valid_park_roles = [r for r in roles if r in ["OPERATOR", "PROPERTY_OWNER"]]
if not valid_park_roles:
return Response(
{
"detail": (
"Park companies must have at least one of: "
"OPERATOR, PROPERTY_OWNER"
)
},
status=status.HTTP_400_BAD_REQUEST,
)
# Create the company
company = ParkCompany.objects.create( # type: ignore
name=validated["name"],
roles=valid_park_roles,
description=validated.get("description", ""),
website=validated.get("website", ""),
founded_date=validated.get("founded_date"),
)
out_serializer = CompanyDetailOutputSerializer(
company, context={"request": request}
)
return Response(out_serializer.data, status=status.HTTP_201_CREATED)
# --- Company retrieve / update / delete ------------------------------------
@extend_schema(
summary="Retrieve, update or delete a park company",
responses={200: CompanyDetailOutputSerializer()},
tags=["Parks", "Companies"],
)
class ParkCompanyDetailAPIView(APIView):
"""
Park Company detail endpoints for OPERATOR and PROPERTY_OWNER companies.
"""
permission_classes = [permissions.AllowAny]
def _get_company_or_404(self, pk: int) -> Any:
if not MODELS_AVAILABLE:
raise NotFound(
(
"Park company detail is not available because domain models "
"are not imported. Implement apps.parks.models.Company "
"to enable detail endpoints."
)
)
try:
# Only allow access to companies with park-related roles
return ParkCompany.objects.filter(
roles__overlap=["OPERATOR", "PROPERTY_OWNER"]
).get(
pk=pk
) # type: ignore
except ParkCompany.DoesNotExist: # type: ignore
raise NotFound("Park company not found")
def get(self, request: Request, pk: int) -> Response:
"""Retrieve a park company."""
company = self._get_company_or_404(pk)
serializer = CompanyDetailOutputSerializer(
company, context={"request": request}
)
return Response(serializer.data)
@extend_schema(
request=CompanyUpdateInputSerializer,
responses={200: CompanyDetailOutputSerializer()},
)
def patch(self, request: Request, pk: int) -> Response:
"""Update a park company."""
company = self._get_company_or_404(pk)
serializer_in = CompanyUpdateInputSerializer(data=request.data, partial=True)
serializer_in.is_valid(raise_exception=True)
validated = serializer_in.validated_data
# If roles are being updated, validate they're appropriate for parks domain
if "roles" in validated:
roles = validated["roles"]
valid_park_roles = [r for r in roles if r in ["OPERATOR", "PROPERTY_OWNER"]]
if not valid_park_roles:
return Response(
{
"detail": (
"Park companies must have at least one of: "
"OPERATOR, PROPERTY_OWNER"
)
},
status=status.HTTP_400_BAD_REQUEST,
)
validated["roles"] = valid_park_roles
if not MODELS_AVAILABLE:
return Response(
{
"detail": (
"Park company update is not available because domain models "
"are not imported."
)
},
status=status.HTTP_501_NOT_IMPLEMENTED,
)
for key, value in validated.items():
setattr(company, key, value)
company.save()
serializer = CompanyDetailOutputSerializer(
company, context={"request": request}
)
return Response(serializer.data)
def put(self, request: Request, pk: int) -> Response:
"""Full replace - reuse patch behavior for simplicity."""
return self.patch(request, pk)
def delete(self, request: Request, pk: int) -> Response:
"""Delete a park company."""
if not MODELS_AVAILABLE:
return Response(
{
"detail": (
"Park company delete is not available because domain models "
"are not imported."
)
},
status=status.HTTP_501_NOT_IMPLEMENTED,
)
company = self._get_company_or_404(pk)
company.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
# --- Company search (enhanced) ---------------------------------------------
@extend_schema(
summary="Search park companies (operators/property owners) for autocomplete",
parameters=[
OpenApiParameter(
name="q", location=OpenApiParameter.QUERY, type=OpenApiTypes.STR
),
OpenApiParameter(
name="roles",
location=OpenApiParameter.QUERY,
type=OpenApiTypes.STR,
description="Filter by roles: OPERATOR, PROPERTY_OWNER (comma-separated)",
),
],
responses={200: OpenApiTypes.OBJECT},
tags=["Parks", "Companies"],
)
class ParkCompanySearchAPIView(APIView):
"""
Enhanced park company search with role filtering.
"""
permission_classes = [permissions.AllowAny]
def get(self, request: Request) -> Response:
q = request.query_params.get("q", "")
if not q:
return Response([], status=status.HTTP_200_OK)
if ParkCompany is None:
# Provide helpful placeholder structure
return Response(
[
{
"id": 1,
"name": "Six Flags Entertainment",
"slug": "six-flags",
"roles": ["OPERATOR"],
},
{
"id": 2,
"name": "Cedar Fair",
"slug": "cedar-fair",
"roles": ["OPERATOR", "PROPERTY_OWNER"],
},
{
"id": 3,
"name": "Disney Parks",
"slug": "disney",
"roles": ["OPERATOR", "PROPERTY_OWNER"],
},
]
)
# Filter to only park-related roles
qs = ParkCompany.objects.filter(
name__icontains=q, roles__overlap=["OPERATOR", "PROPERTY_OWNER"]
).distinct() # type: ignore
# Additional role filtering
roles = request.query_params.get("roles")
if roles:
role_list = [role.strip().upper() for role in roles.split(",")]
valid_roles = [r for r in role_list if r in ["OPERATOR", "PROPERTY_OWNER"]]
if valid_roles:
qs = qs.filter(roles__overlap=valid_roles)
qs = qs[:20] # Limit results
results = [
{
"id": c.id,
"name": c.name,
"slug": getattr(c, "slug", ""),
"roles": c.roles if hasattr(c, "roles") else [],
}
for c in qs
]
return Response(results)

View File

@@ -13,9 +13,13 @@ from .park_views import (
ParkListCreateAPIView,
ParkDetailAPIView,
FilterOptionsAPIView,
CompanySearchAPIView,
ParkSearchSuggestionsAPIView,
)
from .company_views import (
ParkCompanyListCreateAPIView,
ParkCompanyDetailAPIView,
ParkCompanySearchAPIView,
)
from .views import ParkPhotoViewSet
# Create router for nested photo endpoints
@@ -29,10 +33,13 @@ urlpatterns = [
path("", ParkListCreateAPIView.as_view(), name="park-list-create"),
# Filter options
path("filter-options/", FilterOptionsAPIView.as_view(), name="park-filter-options"),
# Company endpoints - domain-specific CRUD for OPERATOR/PROPERTY_OWNER companies
path("companies/", ParkCompanyListCreateAPIView.as_view(), name="park-companies-list-create"),
path("companies/<int:pk>/", ParkCompanyDetailAPIView.as_view(), name="park-company-detail"),
# Autocomplete / suggestion endpoints
path(
"search/companies/",
CompanySearchAPIView.as_view(),
ParkCompanySearchAPIView.as_view(),
name="park-search-companies",
),
path(

View File

@@ -0,0 +1,352 @@
"""
Rides Company API views for ThrillWiki API v1.
This module implements comprehensive Company CRUD endpoints specifically for the
Rides domain:
- Companies with MANUFACTURER and DESIGNER roles
- List / Create: GET /rides/companies/ POST /rides/companies/
- Retrieve / Update / Delete: GET /rides/companies/{pk}/ PATCH/PUT/DELETE
/rides/companies/{pk}/
- Enhanced search: GET /rides/search/companies/?q=...&role=...
These views handle companies that manufacture or design rides, staying within the
Rides domain boundary.
"""
from typing import Any
from rest_framework import status, permissions
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
from rest_framework.exceptions import NotFound, ValidationError
from drf_spectacular.utils import extend_schema, OpenApiParameter
from drf_spectacular.types import OpenApiTypes
# Reuse existing Company serializers
from apps.api.v1.serializers.companies import (
CompanyDetailOutputSerializer,
CompanyCreateInputSerializer,
CompanyUpdateInputSerializer,
)
# Attempt to import Rides Company model; fall back gracefully if not present
try:
from apps.rides.models import Company as RideCompany # type: ignore
MODELS_AVAILABLE = True
except Exception:
RideCompany = None # type: ignore
MODELS_AVAILABLE = False
class StandardResultsSetPagination(PageNumberPagination):
page_size = 20
page_size_query_param = "page_size"
max_page_size = 1000
# --- Company list & create for Rides domain --------------------------------
class RideCompanyListCreateAPIView(APIView):
permission_classes = [permissions.AllowAny]
@extend_schema(
summary="List ride companies with filtering and pagination",
description=(
"List companies with MANUFACTURER and DESIGNER roles for the rides domain."
),
parameters=[
OpenApiParameter(
name="page", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT
),
OpenApiParameter(
name="page_size", location=OpenApiParameter.QUERY, type=OpenApiTypes.INT
),
OpenApiParameter(
name="search",
location=OpenApiParameter.QUERY,
type=OpenApiTypes.STR,
description="Search companies by name",
),
OpenApiParameter(
name="role",
location=OpenApiParameter.QUERY,
type=OpenApiTypes.STR,
description="Filter by role: MANUFACTURER, DESIGNER",
),
],
responses={200: CompanyDetailOutputSerializer(many=True)},
tags=["Rides"],
)
def get(self, request: Request) -> Response:
"""List ride companies with basic filtering and pagination."""
if not MODELS_AVAILABLE:
return Response(
{
"detail": (
"Ride company listing is not available because domain "
"models are not imported. Implement "
"apps.rides.models.Company to enable listing."
)
},
status=status.HTTP_501_NOT_IMPLEMENTED,
)
# Filter to only ride-related roles
qs = RideCompany.objects.filter( # type: ignore
roles__overlap=["MANUFACTURER", "DESIGNER"]
).distinct()
# Basic filters
search_query = request.query_params.get("search")
if search_query:
qs = qs.filter(name__icontains=search_query)
role_filter = request.query_params.get("role")
if role_filter and role_filter in ["MANUFACTURER", "DESIGNER"]:
qs = qs.filter(roles__contains=[role_filter])
paginator = StandardResultsSetPagination()
page = paginator.paginate_queryset(qs, request)
serializer = CompanyDetailOutputSerializer(
page, many=True, context={"request": request}
)
return paginator.get_paginated_response(serializer.data)
@extend_schema(
summary="Create a new ride company",
description=(
"Create a new company with MANUFACTURER and/or DESIGNER roles "
"for the rides domain."
),
request=CompanyCreateInputSerializer,
responses={201: CompanyDetailOutputSerializer()},
tags=["Rides"],
)
def post(self, request: Request) -> Response:
"""Create a new ride company."""
if not MODELS_AVAILABLE:
return Response(
{
"detail": (
"Ride company creation is not available because domain "
"models are not imported. Implement "
"apps.rides.models.Company to enable creation."
)
},
status=status.HTTP_501_NOT_IMPLEMENTED,
)
serializer_in = CompanyCreateInputSerializer(data=request.data)
serializer_in.is_valid(raise_exception=True)
validated = serializer_in.validated_data
# Validate roles for rides domain
roles = validated.get("roles", [])
valid_ride_roles = [
role for role in roles if role in ["MANUFACTURER", "DESIGNER"]
]
if not valid_ride_roles:
raise ValidationError(
{
"roles": (
"At least one role must be MANUFACTURER or DESIGNER "
"for ride companies."
)
}
)
# Only keep valid ride roles
if len(valid_ride_roles) != len(roles):
validated["roles"] = valid_ride_roles
# Create the company
company = RideCompany.objects.create( # type: ignore
name=validated["name"],
slug=validated.get("slug", ""),
roles=validated["roles"],
description=validated.get("description", ""),
website=validated.get("website", ""),
founded_date=validated.get("founded_date"),
)
out_serializer = CompanyDetailOutputSerializer(
company, context={"request": request}
)
return Response(out_serializer.data, status=status.HTTP_201_CREATED)
# --- Company retrieve / update / delete ------------------------------------
class RideCompanyDetailAPIView(APIView):
permission_classes = [permissions.AllowAny]
def _get_company_or_404(self, pk: int) -> Any:
if not MODELS_AVAILABLE:
raise NotFound(
(
"Ride company detail is not available because domain models "
"are not imported. Implement apps.rides.models.Company to "
"enable detail endpoints."
)
)
try:
return RideCompany.objects.filter(
roles__overlap=["MANUFACTURER", "DESIGNER"]
).get(pk=pk)
except RideCompany.DoesNotExist:
raise NotFound("Ride company not found")
@extend_schema(
summary="Retrieve a ride company",
responses={200: CompanyDetailOutputSerializer()},
tags=["Rides"],
)
def get(self, request: Request, pk: int) -> Response:
"""Retrieve a ride company."""
company = self._get_company_or_404(pk)
serializer = CompanyDetailOutputSerializer(
company, context={"request": request}
)
return Response(serializer.data)
@extend_schema(
request=CompanyUpdateInputSerializer,
responses={200: CompanyDetailOutputSerializer()},
tags=["Rides"],
)
def patch(self, request: Request, pk: int) -> Response:
"""Update a ride company."""
company = self._get_company_or_404(pk)
serializer_in = CompanyUpdateInputSerializer(data=request.data, partial=True)
serializer_in.is_valid(raise_exception=True)
validated = serializer_in.validated_data
# Validate roles for rides domain if being updated
if "roles" in validated:
roles = validated["roles"]
valid_ride_roles = [
role for role in roles if role in ["MANUFACTURER", "DESIGNER"]
]
if not valid_ride_roles:
raise ValidationError(
{
"roles": (
"At least one role must be MANUFACTURER or DESIGNER "
"for ride companies."
)
}
)
# Only keep valid ride roles
validated["roles"] = valid_ride_roles
# Update the company
for key, value in validated.items():
setattr(company, key, value)
company.save()
serializer = CompanyDetailOutputSerializer(
company, context={"request": request}
)
return Response(serializer.data)
def put(self, request: Request, pk: int) -> Response:
"""Full replace - reuse patch behavior for simplicity."""
return self.patch(request, pk)
@extend_schema(
summary="Delete a ride company",
responses={204: None},
tags=["Rides"],
)
def delete(self, request: Request, pk: int) -> Response:
"""Delete a ride company."""
company = self._get_company_or_404(pk)
company.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
# --- Enhanced Company search (autocomplete) for Rides domain ---------------
class RideCompanySearchAPIView(APIView):
permission_classes = [permissions.AllowAny]
@extend_schema(
summary="Search ride companies (manufacturers/designers) for autocomplete",
description=(
"Enhanced search for companies with MANUFACTURER and DESIGNER roles."
),
parameters=[
OpenApiParameter(
name="q",
location=OpenApiParameter.QUERY,
type=OpenApiTypes.STR,
description="Search query for company names",
),
OpenApiParameter(
name="role",
location=OpenApiParameter.QUERY,
type=OpenApiTypes.STR,
description="Filter by specific role: MANUFACTURER, DESIGNER",
),
],
responses={200: OpenApiTypes.OBJECT},
tags=["Rides"],
)
def get(self, request: Request) -> Response:
"""Enhanced search for ride companies with role filtering."""
q = request.query_params.get("q", "")
role_filter = request.query_params.get("role", "")
if not q:
return Response([], status=status.HTTP_200_OK)
if RideCompany is None:
# Provide helpful placeholder structure
return Response(
[
{
"id": 1,
"name": "Rocky Mountain Construction",
"slug": "rmc",
"roles": ["MANUFACTURER"],
},
{
"id": 2,
"name": "Bolliger & Mabillard",
"slug": "b&m",
"roles": ["MANUFACTURER"],
},
{
"id": 3,
"name": "Alan Schilke",
"slug": "alan-schilke",
"roles": ["DESIGNER"],
},
]
)
# Filter to only ride-related roles
qs = RideCompany.objects.filter(
name__icontains=q, roles__overlap=["MANUFACTURER", "DESIGNER"]
)
# Apply role filter if specified
if role_filter and role_filter in ["MANUFACTURER", "DESIGNER"]:
qs = qs.filter(roles__contains=[role_filter])
qs = qs[:20] # Limit results
results = [
{
"id": c.id,
"name": c.name,
"slug": getattr(c, "slug", ""),
"roles": c.roles if hasattr(c, "roles") else [],
}
for c in qs
]
return Response(results)

View File

@@ -15,10 +15,14 @@ from .views import (
RideListCreateAPIView,
RideDetailAPIView,
FilterOptionsAPIView,
CompanySearchAPIView,
RideModelSearchAPIView,
RideSearchSuggestionsAPIView,
)
from .company_views import (
RideCompanyListCreateAPIView,
RideCompanyDetailAPIView,
RideCompanySearchAPIView,
)
from .photo_views import RidePhotoViewSet
# Create router for nested photo endpoints
@@ -32,10 +36,15 @@ urlpatterns = [
path("", RideListCreateAPIView.as_view(), name="ride-list-create"),
# Filter options
path("filter-options/", FilterOptionsAPIView.as_view(), name="ride-filter-options"),
# Company endpoints - domain-specific CRUD for MANUFACTURER/DESIGNER companies
path("companies/", RideCompanyListCreateAPIView.as_view(),
name="ride-companies-list-create"),
path("companies/<int:pk>/", RideCompanyDetailAPIView.as_view(),
name="ride-company-detail"),
# Autocomplete / suggestion endpoints
path(
"search/companies/",
CompanySearchAPIView.as_view(),
RideCompanySearchAPIView.as_view(),
name="ride-search-companies",
),
path(

View File

@@ -103,7 +103,7 @@ class HealthCheckAPIView(APIView):
}
# Process individual health checks
for plugin in plugins:
for plugin in plugins.values():
plugin_name = plugin.identifier()
plugin_errors = (
errors.get(plugin.__class__.__name__, [])
@@ -127,7 +127,7 @@ class HealthCheckAPIView(APIView):
# Check if any critical services are failing
critical_errors = any(
getattr(plugin, "critical_service", False)
for plugin in plugins
for plugin in plugins.values()
if isinstance(errors, dict) and errors.get(plugin.__class__.__name__)
)
status_code = 503 if critical_errors else 200

View File

@@ -4,6 +4,9 @@ Local development settings for thrillwiki project.
from ..settings import database
import logging
import os
from decouple import config
import re
from .base import (
BASE_DIR,
INSTALLED_APPS,
@@ -45,6 +48,31 @@ CSRF_TRUSTED_ORIGINS = [
"https://beta.thrillwiki.com",
]
CORS_ALLOWED_ORIGIN_REGEXES = [
# Matches http://localhost:3000, http://localhost:3001, etc.
r"^http://localhost:\d+$",
# Matches http://127.0.0.1:3000, http://127.0.0.1:8080, etc.
r"^http://127\.0\.0\.1:\d+$",
]
CORS_ALLOW_HEADERS = [
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
'x-nextjs-data', # Next.js specific header
]
if DEBUG:
CORS_ALLOW_ALL_ORIGINS = True # ⚠️ Only for development!
else:
CORS_ALLOW_ALL_ORIGINS = False
GDAL_LIBRARY_PATH = "/opt/homebrew/lib/libgdal.dylib"
GEOS_LIBRARY_PATH = "/opt/homebrew/lib/libgeos_c.dylib"

2652
backend/pixi.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -77,3 +77,16 @@ stubPath = "stubs"
[tool.uv.sources]
python-json-logger = { url = "https://github.com/nhairs/python-json-logger/releases/download/v3.0.0/python_json_logger-3.0.0-py3-none-any.whl" }
[tool.pixi.workspace]
channels = ["conda-forge"]
platforms = ["osx-arm64"]
[tool.pixi.pypi-dependencies]
thrillwiki = { path = ".", editable = true }
[tool.pixi.environments]
default = { solve-group = "default" }
dev = { features = ["dev"], solve-group = "default" }
[tool.pixi.tasks]

Binary file not shown.