@@ -265,8 +265,14 @@ def _make_request(
265265
266266 try :
267267 response = self ._session .request (method , url , json = data , headers = headers )
268+
268269 response .raise_for_status ()
269270
271+ # Handle empty responses
272+ if not response .text .strip ():
273+ logger .debug ("Empty response body" )
274+ return {}
275+
270276 return response .json ()
271277
272278 except requests .exceptions .HTTPError as e :
@@ -628,6 +634,11 @@ def get_meal_sittings(self, start_date, end_date):
628634
629635 result = self ._make_request ("GET" , endpoint )
630636
637+ # Handle case where result might be None
638+ if result is None :
639+ logger .warning ("Meal sittings API returned None response" )
640+ return []
641+
631642 # Handle JSON:API format response
632643 data = result .get ("data" , [])
633644 included = result .get ("included" , [])
@@ -643,15 +654,17 @@ def get_meal_sittings(self, start_date, end_date):
643654 relationships = item .get ("relationships" , {})
644655
645656 # Get meal category info
646- meal_category_data = relationships .get ("meal_category" , {}).get ("data" , {})
647- meal_category_id = meal_category_data .get ("id" )
657+ meal_category_rel = relationships .get ("meal_category" , {})
658+ meal_category_data = meal_category_rel .get ("data" ) if meal_category_rel else None
659+ meal_category_id = meal_category_data .get ("id" ) if meal_category_data else None
648660 meal_category_label = None
649661 if meal_category_id and meal_category_id in meal_categories :
650662 meal_category_label = meal_categories [meal_category_id ]["attributes" ]["label" ]
651663
652664 # Get meal recipe info
653- meal_recipe_data = relationships .get ("meal_recipe" , {}).get ("data" , {})
654- meal_recipe_id = meal_recipe_data .get ("id" )
665+ meal_recipe_rel = relationships .get ("meal_recipe" , {})
666+ meal_recipe_data = meal_recipe_rel .get ("data" ) if meal_recipe_rel else None
667+ meal_recipe_id = meal_recipe_data .get ("id" ) if meal_recipe_data else None
655668 meal_recipe_summary = None
656669 if meal_recipe_id and meal_recipe_id in meal_recipes :
657670 meal_recipe_summary = meal_recipes [meal_recipe_id ]["attributes" ]["summary" ]
@@ -702,72 +715,59 @@ def create_meal_sitting(self, name: str, date, meal_type: str):
702715 try :
703716 logger .debug (f"Creating meal sitting in Skylight: { name } on { date } ({ meal_type } )" )
704717
705- # First, we need to get available meal categories to find the right ID
706- # For now, let's create a placeholder implementation
707- # TODO: Add meal category lookup
718+ # Get correct meal category ID from API
708719 meal_category_id = self ._get_meal_category_id (meal_type )
709-
710- # Create meal recipe first
711- recipe_data = {
712- "data" : {
713- "type" : "meal_recipe" ,
714- "attributes" : {
715- "summary" : name ,
716- "description" : None
717- },
718- "relationships" : {
719- "meal_category" : {
720- "data" : {
721- "id" : meal_category_id ,
722- "type" : "meal_category"
723- }
724- }
725- }
726- }
720+ if not meal_category_id :
721+ raise Exception (f"Could not find meal category for type: { meal_type } " )
722+
723+ # Use the actual payload format discovered from browser inspection
724+ sitting_data = {
725+ "meal_recipe_id" : None ,
726+ "meal_category_id" : meal_category_id ,
727+ "add_to_grocery_list" : False ,
728+ "date" : date .isoformat (),
729+ "note" : "" ,
730+ "rrule" : None ,
731+ "summary" : name ,
732+ "description" : "" ,
733+ "saveToRecipeBox" : False
727734 }
728735
729- # Create the meal sitting with proper JSON:API format
730- date_range_params = f"?date_min={ date .isoformat ()} &date_max={ date .isoformat ()} &include=meal_category%2Cmeal_recipe"
731- endpoint = f"/frames/{ self .frame_id } /meals/sittings{ date_range_params } "
736+ # Use the correct endpoint with date range parameters (as seen in browser)
737+ params_str = f"?date_min={ date .isoformat ()} &date_max={ date .isoformat ()} &include=meal_category%2Cmeal_recipe"
738+ endpoint = f"/frames/{ self .frame_id } /meals/sittings{ params_str } "
732739
733- # Use the structure from the API request you provided
734- data = {
735- "data" : [
736- {
737- "type" : "meal_sitting" ,
738- "attributes" : {
739- "summary" : name ,
740- "description" : None ,
741- "note" : None ,
742- "rrule" : None ,
743- "recurring" : False ,
744- "instances" : [date .isoformat ()]
745- },
746- "relationships" : {
747- "meal_category" : {
748- "data" : {
749- "id" : meal_category_id ,
750- "type" : "meal_category"
751- }
752- }
753- }
754- }
755- ],
756- "meta" : {
757- "date_min" : date .isoformat (),
758- "date_max" : date .isoformat ()
759- }
760- }
740+ result = self ._make_request ("POST" , endpoint , sitting_data )
761741
762- result = self ._make_request ("POST" , endpoint , data )
742+ # Log the response to understand the format
743+ logger .debug (f"Meal creation response: { result } (type: { type (result )} )" )
763744
764745 # Extract created meal ID from response
765- created_meals = result .get ("data" , [])
766- if created_meals :
767- meal_id = created_meals [0 ].get ("id" )
768- if meal_id :
746+ if isinstance (result , dict ):
747+ # JSON:API format response
748+ created_meal = result .get ("data" , {})
749+ if created_meal :
750+ meal_id = created_meal .get ("id" )
751+ if meal_id :
752+ logger .info (f"Created meal sitting in Skylight: { name } (id={ meal_id } )" )
753+ return str (meal_id )
754+
755+ # If no data field, try direct response
756+ if "id" in result :
757+ meal_id = result ["id" ]
769758 logger .info (f"Created meal sitting in Skylight: { name } (id={ meal_id } )" )
770759 return str (meal_id )
760+ elif isinstance (result , list ):
761+ # Response is a list - take the first item
762+ if result and len (result ) > 0 :
763+ first_item = result [0 ]
764+ if isinstance (first_item , dict ) and "id" in first_item :
765+ meal_id = first_item ["id" ]
766+ logger .info (f"Created meal sitting in Skylight: { name } (id={ meal_id } )" )
767+ return str (meal_id )
768+
769+ # Log the full response to understand the format
770+ logger .warning (f"Unexpected response format from meal creation: { result } " )
771771
772772 raise Exception ("No meal ID returned from create request" )
773773
@@ -776,16 +776,62 @@ def create_meal_sitting(self, name: str, date, meal_type: str):
776776 raise
777777
778778 def _get_meal_category_id (self , meal_type : str ):
779- """Get meal category ID for the given meal type (placeholder implementation)"""
780- # TODO: Implement proper meal category lookup
781- # For now, return a default ID based on meal type
782- category_map = {
783- "breakfast" : "1" ,
784- "lunch" : "2" ,
785- "dinner" : "3" ,
786- "snack" : "4"
787- }
788- return category_map .get (meal_type .lower (), "1" )
779+ """Get meal category ID for the given meal type using dedicated categories API"""
780+ try :
781+ # Use the dedicated meal categories endpoint
782+ result = self ._make_request ("GET" , f"/frames/{ self .frame_id } /meals/categories" )
783+
784+ logger .debug (f"Categories API response: { result } (type: { type (result )} )" )
785+
786+ # Handle both dict and list response formats
787+ categories_data = []
788+ if isinstance (result , dict ):
789+ categories_data = result .get ("data" , [])
790+ elif isinstance (result , list ):
791+ categories_data = result
792+ else :
793+ logger .error (f"Unexpected categories response type: { type (result )} " )
794+ return None
795+
796+ # Extract meal categories from response data
797+ meal_categories = {}
798+ for item in categories_data :
799+ if item .get ("type" ) == "meal_category" :
800+ label = item ["attributes" ]["label" ].lower ()
801+ meal_categories [label ] = item ["id" ]
802+
803+ logger .debug (f"Available meal categories: { meal_categories } " )
804+
805+ # If no categories found, return None to trigger error
806+ if not meal_categories :
807+ logger .error ("No meal categories found in Skylight frame" )
808+ return None
809+
810+ # Map meal type to category
811+ meal_type_lower = meal_type .lower ()
812+ if meal_type_lower in meal_categories :
813+ logger .debug (f"Found exact match for meal type '{ meal_type } ': { meal_categories [meal_type_lower ]} " )
814+ return meal_categories [meal_type_lower ]
815+
816+ # Try some common mappings
817+ type_mapping = {
818+ "snack" : "snacks" , # Sometimes it's plural
819+ }
820+
821+ mapped_type = type_mapping .get (meal_type_lower , meal_type_lower )
822+ if mapped_type in meal_categories :
823+ logger .warning (f"Meal type '{ meal_type } ' not found, using '{ mapped_type } ' category" )
824+ return meal_categories [mapped_type ]
825+
826+ # Use any available category as last resort
827+ default_id = list (meal_categories .values ())[0 ]
828+ default_label = list (meal_categories .keys ())[0 ]
829+ logger .warning (f"Meal type '{ meal_type } ' not found, using available category '{ default_label } ' (ID: { default_id } )" )
830+ return default_id
831+
832+ except Exception as e :
833+ logger .error (f"Failed to get meal category ID: { e } " )
834+ return None
789835
790836 def update_meal_sitting (self , sitting_id : str , name : str , date , meal_type : str ):
791837 """
@@ -801,35 +847,35 @@ def update_meal_sitting(self, sitting_id: str, name: str, date, meal_type: str):
801847 logger .debug (f"Updating meal sitting in Skylight: { sitting_id } " )
802848
803849 meal_category_id = self ._get_meal_category_id (meal_type )
804-
805- # Update using the meal sittings endpoint
806- date_range_params = f"?date_min={ date .isoformat ()} &date_max={ date .isoformat ()} &include=meal_category%2Cmeal_recipe"
807- endpoint = f"/frames/{ self .frame_id } /meals/sittings/{ sitting_id } { date_range_params } "
808-
809- data = {
810- "data" : {
811- "type" : "meal_sitting" ,
812- "id" : sitting_id ,
813- "attributes" : {
814- "summary" : name ,
815- "description" : None ,
816- "note" : None ,
817- "rrule" : None ,
818- "recurring" : False ,
819- "instances" : [date .isoformat ()]
820- },
821- "relationships" : {
822- "meal_category" : {
823- "data" : {
824- "id" : meal_category_id ,
825- "type" : "meal_category"
826- }
827- }
828- }
829- }
850+ if not meal_category_id :
851+ raise Exception (f"Could not find meal category for type: { meal_type } " )
852+
853+ # Use the same payload format as creation (discovered from browser)
854+ sitting_data = {
855+ "meal_recipe_id" : None ,
856+ "meal_category_id" : meal_category_id ,
857+ "add_to_grocery_list" : False ,
858+ "date" : date .isoformat (),
859+ "note" : "" ,
860+ "rrule" : None ,
861+ "summary" : name ,
862+ "description" : "" ,
863+ "saveToRecipeBox" : False
830864 }
831865
832- result = self ._make_request ("PATCH" , endpoint , data )
866+ # Try instance-based update first (similar to delete endpoint)
867+ try :
868+ date_params = f"?date_min={ date .isoformat ()} &date_max={ date .isoformat ()} &include=meal_category%2Cmeal_recipe"
869+ endpoint = f"/frames/{ self .frame_id } /meals/sittings/{ sitting_id } /instances/{ date .isoformat ()} { date_params } "
870+ result = self ._make_request ("PATCH" , endpoint , sitting_data )
871+ logger .info (f"Updated meal sitting instance in Skylight: { sitting_id } " )
872+ return
873+ except Exception as e :
874+ logger .warning (f"Instance-based update failed: { e } , trying direct update" )
875+
876+ # Fallback to simple endpoint for updates
877+ endpoint = f"/frames/{ self .frame_id } /meals/sittings/{ sitting_id } "
878+ result = self ._make_request ("PATCH" , endpoint , sitting_data )
833879 logger .info (f"Updated meal sitting in Skylight: { sitting_id } " )
834880
835881 except Exception as e :
@@ -847,12 +893,10 @@ def delete_meal_sitting(self, sitting_id: str, date=None):
847893 try :
848894 logger .debug (f"Deleting meal sitting from Skylight: { sitting_id } " )
849895
850- # Use the correct endpoint format based on the API request you provided
851896 if date :
852- # Delete specific instance (date-specific meal)
853- endpoint = f"/frames/{ self .frame_id } /meals/sittings/{ sitting_id } /instances/{ date .isoformat ()} "
897+ # Delete specific instance (date-specific meal) - using discovered format
854898 date_params = f"?date_min={ date .isoformat ()} &date_max={ date .isoformat ()} &include=meal_category%2Cmeal_recipe"
855- endpoint += date_params
899+ endpoint = f"/frames/ { self . frame_id } /meals/sittings/ { sitting_id } /instances/ { date . isoformat () } { date_params } "
856900 else :
857901 # Delete entire meal sitting
858902 endpoint = f"/frames/{ self .frame_id } /meals/sittings/{ sitting_id } "
0 commit comments