diff --git a/.gitignore b/.gitignore
index 0652183..9a91afa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,8 @@ __pycache__
.DS_Store
*.log
*.egg-info
-build
\ No newline at end of file
+build
+
+# Experiment artifacts
+*.png
+*.pkl
\ No newline at end of file
diff --git a/deathstar/entities/user.py b/deathstar/entities/user.py
deleted file mode 100644
index 0234e91..0000000
--- a/deathstar/entities/user.py
+++ /dev/null
@@ -1,79 +0,0 @@
-from typing import Any
-from cascade.dataflow.dataflow import DataFlow, Edge, InvokeMethod, OpNode
-from cascade.dataflow.operator import StatefulOperator
-from deathstar.entities.flight import Flight, flight_op
-from deathstar.entities.hotel import Hotel, hotel_op
-
-
-class User():
- def __init__(self, user_id: str, password: str):
- self.id = user_id
- self.password = password
-
- def check(self, password):
- return self.password == password
-
- def order(self, flight: Flight, hotel: Hotel):
- if hotel.reserve() and flight.reserve():
- return True
- else:
- return False
-
-#### COMPILED FUNCTIONS (ORACLE) #####
-
-def check_compiled(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.pop()
- return state.password == variable_map["password"]
-
-def order_compiled_entry_0(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.append(variable_map["hotel"])
-
-def order_compiled_entry_1(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.append(variable_map["flight"])
-
-def order_compiled_if_cond(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- return variable_map["hotel_reserve"] and variable_map["flight_reserve"]
-
-def order_compiled_if_body(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.pop()
- return True
-
-def order_compiled_else_body(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.pop()
- return False
-
-user_op = StatefulOperator(
- User,
- {
- "login": check_compiled,
- "order_compiled_entry_0": order_compiled_entry_0,
- "order_compiled_entry_1": order_compiled_entry_1,
- "order_compiled_if_cond": order_compiled_if_cond,
- "order_compiled_if_body": order_compiled_if_body,
- "order_compiled_else_body": order_compiled_else_body
- },
- {}
-)
-
-# For now, the dataflow will be serial instead of parallel. Future optimizations
-# will try to automatically parallelize this.
-# There is also no user entry (this could also be an optimization)
-df = DataFlow("user_order")
-n0 = OpNode(user_op, InvokeMethod("order_compiled_entry_0"))
-n1 = OpNode(hotel_op, InvokeMethod("reserve"), assign_result_to="hotel_reserve")
-n2 = OpNode(user_op, InvokeMethod("order_compiled_entry_1"))
-n3 = OpNode(flight_op, InvokeMethod("reserve"), assign_result_to="flight_reserve")
-n4 = OpNode(user_op, InvokeMethod("order_compiled_if_cond"), is_conditional=True)
-n5 = OpNode(user_op, InvokeMethod("order_compiled_if_body"))
-n6 = OpNode(user_op, InvokeMethod("order_compiled_else_body"))
-
-df.add_edge(Edge(n0, n1))
-df.add_edge(Edge(n1, n2))
-df.add_edge(Edge(n2, n3))
-df.add_edge(Edge(n3, n4))
-df.add_edge(Edge(n4, n5, if_conditional=True))
-df.add_edge(Edge(n4, n6, if_conditional=False))
-
-df.entry = n0
-
-user_op.dataflows["order"] = df
diff --git a/deathstar/__init__.py b/deathstar_hotel_reservation/__init__.py
similarity index 100%
rename from deathstar/__init__.py
rename to deathstar_hotel_reservation/__init__.py
diff --git a/deathstar/demo.py b/deathstar_hotel_reservation/demo.py
similarity index 70%
rename from deathstar/demo.py
rename to deathstar_hotel_reservation/demo.py
index a284ee7..b54d643 100644
--- a/deathstar/demo.py
+++ b/deathstar_hotel_reservation/demo.py
@@ -1,358 +1,375 @@
-import random
-import sys
-import os
-import time
-import csv
-from timeit import default_timer as timer
-from multiprocessing import Pool
-
-# import cascade
-sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
-
-from cascade.dataflow.dataflow import Event, InitClass, InvokeMethod, OpNode
-from cascade.runtime.flink_runtime import FlinkClientSync, FlinkOperator, FlinkRuntime, FlinkStatelessOperator
-from deathstar.entities.flight import Flight, flight_op
-from deathstar.entities.hotel import Geo, Hotel, Rate, hotel_op
-from deathstar.entities.recommendation import Recommendation, recommend_op
-from deathstar.entities.search import Search, search_op
-from deathstar.entities.user import User, user_op
-
-
-class DeathstarDemo():
- def __init__(self):
- self.init_user = OpNode(user_op, InitClass())
- self.init_hotel = OpNode(hotel_op, InitClass())
- self.init_flight = OpNode(flight_op, InitClass())
-
- def init_runtime(self, runtime, **kwargs):
- self.runtime = runtime
- self.runtime.init(**kwargs)
- self.runtime.add_operator(hotel_op)
- self.runtime.add_operator(flight_op)
- self.runtime.add_operator(user_op)
- self.runtime.add_stateless_operator(search_op)
- self.runtime.add_stateless_operator(recommend_op)
-
-
- def populate(self):
- # Create locations & rates for hotels
- geos = []
- geos.append(Geo(37.7867, 0))
- geos.append(Geo(37.7854, -122.4005))
- geos.append(Geo(37.7867, -122.4071))
- geos.append(Geo(37.7936, -122.3930))
- geos.append(Geo(37.7831, -122.4181))
- geos.append(Geo(37.7863, -122.4015))
-
- for i in range(6, 100):
- lat: float = 37.7835 + i / 500.0 * 3
- lon: float = -122.41 + i / 500.0 * 4
- geos.append(Geo(lat, lon))
-
- rates = {}
- rates[1] = Rate(1, "RACK",
- "2015-04-09",
- "2015-04-10",
- { "BookableRate": 190.0,
- "Code": "KNG",
- "RoomDescription": "King sized bed",
- "TotalRate": 109.0,
- "TotalRateInclusive": 123.17})
-
- rates[2] = Rate(2, "RACK",
- "2015-04-09",
- "2015-04-10",
- { "BookableRate": 139.0,
- "Code": "QN",
- "RoomDescription": "Queen sized bed",
- "TotalRate": 139.0,
- "TotalRateInclusive": 153.09})
-
- rates[3] = Rate(3, "RACK",
- "2015-04-09",
- "2015-04-10",
- { "BookableRate": 109.0,
- "Code": "KNG",
- "RoomDescription": "King sized bed",
- "TotalRate": 109.0,
- "TotalRateInclusive": 123.17})
-
- for i in range(4, 80):
- if i % 3 == 0:
- hotel_id = i
- end_date = "2015-04-"
- rate = 109.0
- rate_inc = 123.17
- if i % 2 == 0:
- end_date += '17'
- else:
- end_date += '24'
- if i % 5 == 1:
- rate = 120.0
- rate_inc = 140.0
- elif i % 5 == 2:
- rate = 124.0
- rate_inc = 144.0
- elif i % 5 == 3:
- rate = 132.0
- rate_inc = 158.0
- elif i % 5 == 4:
- rate = 232.0
- rate_inc = 258.0
-
- rates[hotel_id] = Rate(i, "RACK",
- "2015-04-09",
- end_date,
- { "BookableRate": rate,
- "Code": "KNG",
- "RoomDescription": "King sized bed",
- "TotalRate": rate,
- "TotalRateInclusive": rate_inc})
-
- # we don't create recommendations, because it doesn't really
- # correspond to an entity
- prices = []
-
- prices.append(150.00)
- prices.append(120.00)
- prices.append(190.00)
- prices.append(160.00)
- prices.append(140.00)
- prices.append(200.00)
-
- for i in range(6, 100):
- price = 179.00
- if i % 3 == 0:
- if i % 5 == 0:
- price = 123.17
- elif i % 5 == 1:
- price = 140.00
- elif i % 5 == 2:
- price = 144.00
- elif i % 5 == 3:
- price = 158.00
- elif i % 5 == 4:
- price = 258.00
-
- prices.append(price)
-
- # populate users
- self.users = [User(f"Cornell_{i}", str(i) * 10) for i in range(501)]
- for user in self.users:
- event = Event(self.init_user, [user.id], {"user_id": user.id, "password": user.password}, None)
- self.runtime.send(event)
-
- # populate hotels
- self.hotels: list[Hotel] = []
- for i in range(100):
- geo = geos[i]
- rate = rates[i] if i in rates else []
- price = prices[i]
- hotel = Hotel(str(i), 10, geo, rate, price)
- self.hotels.append(hotel)
- event = Event(self.init_hotel, [hotel.key],
- {
- "key": hotel.key,
- "cap": hotel.cap,
- "geo": hotel.geo,
- "rates": hotel.rates,
- "price": hotel.price
- }, None)
- self.runtime.send(event)
-
- # populate flights
- self.flights = [Flight(str(i), 10) for i in range(100)]
- for flight in self.flights[:-1]:
- event = Event(self.init_flight, [flight.id], {
- "id": flight.id,
- "cap": flight.cap
- }, None)
- self.runtime.send(event)
- flight = self.flights[-1]
- event = Event(self.init_flight, [flight.id], {
- "id": flight.id,
- "cap": flight.cap
- }, None)
- self.runtime.send(event, flush=True)
-
-def search_hotel():
- in_date = random.randint(9, 23)
- out_date = random.randint(in_date + 1, 24)
-
- if in_date < 10:
- in_date_str = f"2015-04-0{in_date}"
- else:
- in_date_str = f"2015-04-{in_date}"
- if out_date < 10:
- out_date_str = f"2015-04-0{out_date}"
- else:
- out_date_str = f"2015-04-{out_date}"
-
- lat = 38.0235 + (random.randint(0, 481) - 240.5) / 1000.0
- lon = -122.095 + (random.randint(0, 325) - 157.0) / 1000.0
-
- # We don't really use the in_date, out_date information
- return Event(search_op.dataflow.entry, ["tempkey"], {"lat": lat, "lon": lon}, search_op.dataflow)
-
-def recommend(req_param=None):
- if req_param is None:
- coin = random.random()
- if coin < 0.5:
- req_param = "distance"
- else:
- req_param = "price"
-
- lat = 38.0235 + (random.randint(0, 481) - 240.5) / 1000.0
- lon = -122.095 + (random.randint(0, 325) - 157.0) / 1000.0
-
- return Event(recommend_op.dataflow.entry, ["tempkey"], {"requirement": req_param, "lat": lat, "lon": lon}, recommend_op.dataflow)
-
-def user_login(succesfull=True):
- user_id = random.randint(0, 500)
- username = f"Cornell_{user_id}"
- password = str(user_id) * 10 if succesfull else ""
- return Event(OpNode(user_op, InvokeMethod("login")), [username], {"password": password}, None)
-
-def reserve():
- hotel_id = random.randint(0, 99)
- flight_id = random.randint(0, 99)
-
- # user = User("user1", "pass")
- # user.order(flight, hotel)
- user_id = "Cornell_" + str(random.randint(0, 500))
-
- return Event(user_op.dataflows["order"].entry, [user_id], {"flight": str(flight_id), "hotel": str(hotel_id)}, user_op.dataflows["order"])
-
-def deathstar_workload_generator():
- search_ratio = 0.6
- recommend_ratio = 0.39
- user_ratio = 0.005
- reserve_ratio = 0.005
- c = 0
- while True:
- coin = random.random()
- if coin < search_ratio:
- yield search_hotel()
- elif coin < search_ratio + recommend_ratio:
- yield recommend()
- elif coin < search_ratio + recommend_ratio + user_ratio:
- yield user_login()
- else:
- yield reserve()
- c += 1
-
-threads = 1
-messages_per_second = 10
-sleeps_per_second = 10
-sleep_time = 0.0085
-seconds = 10
-
-
-def benchmark_runner(proc_num) -> dict[int, dict]:
- print(f'Generator: {proc_num} starting')
- client = FlinkClientSync("deathstar", "ds-out", "localhost:9092", True)
- deathstar_generator = deathstar_workload_generator()
- # futures: dict[int, dict] = {}
- start = timer()
- for _ in range(seconds):
- sec_start = timer()
- for i in range(messages_per_second):
- if i % (messages_per_second // sleeps_per_second) == 0:
- time.sleep(sleep_time)
- event = next(deathstar_generator)
- # func_name = event.dataflow.name if event.dataflow is not None else "login" # only login has no dataflow
- key = event.key_stack[0]
- # params = event.variable_map
- client.send(event)
- # futures[event._id] = {"event": f'{func_name} {key}->{params}'}
-
- client.flush()
- sec_end = timer()
- lps = sec_end - sec_start
- if lps < 1:
- time.sleep(1 - lps)
- sec_end2 = timer()
- print(f'Latency per second: {sec_end2 - sec_start}')
- end = timer()
- print(f'Average latency per second: {(end - start) / seconds}')
- # styx.close()
- # for key, metadata in styx.delivery_timestamps.items():
- # timestamp_futures[key]["timestamp"] = metadata
-
- done = False
- while not done:
- done = True
- for event_id, fut in client._futures.items():
- result = fut["ret"]
- if result is None:
- done = False
- time.sleep(0.5)
- break
- futures = client._futures
- client.close()
- return futures
-
-
-def write_dict_to_csv(futures_dict, filename):
- """
- Writes a dictionary of event data to a CSV file.
-
- Args:
- futures_dict (dict): A dictionary where each key is an event ID and the value is another dict.
- filename (str): The name of the CSV file to write to.
- """
- # Define the column headers
- headers = ["event_id", "sent", "sent_t", "ret", "ret_t", "latency"]
-
- # Open the file for writing
- with open(filename, mode='w', newline='', encoding='utf-8') as csvfile:
- writer = csv.DictWriter(csvfile, fieldnames=headers)
-
- # Write the headers
- writer.writeheader()
-
- # Write the data rows
- for event_id, event_data in futures_dict.items():
- # Prepare a row where the 'event_id' is the first column
- row = {
- "event_id": event_id,
- "sent": event_data.get("sent"),
- "sent_t": event_data.get("sent_t"),
- "ret": event_data.get("ret"),
- "ret_t": event_data.get("ret_t"),
- "latency": event_data["ret_t"][1] - event_data["sent_t"][1]
- }
- writer.writerow(row)
-
-def main():
- ds = DeathstarDemo()
- ds.init_runtime(FlinkRuntime("deathstar", "ds-out"), bundle_time=5, bundle_size=10)
- ds.runtime.run(run_async=True)
- ds.populate()
-
-
- time.sleep(1)
- input()
-
- # with Pool(threads) as p:
- # results = p.map(benchmark_runner, range(threads))
-
- # results = {k: v for d in results for k, v in d.items()}
- results = benchmark_runner(0)
-
- # pd.DataFrame({"request_id": list(results.keys()),
- # "timestamp": [res["timestamp"] for res in results.values()],
- # "op": [res["op"] for res in results.values()]
- # }).sort_values("timestamp").to_csv(f'{SAVE_DIR}/client_requests.csv', index=False)
- print(results)
- t = len(results)
- r = 0
- for result in results.values():
- if result["ret"] is not None:
- print(result)
- r += 1
- print(f"{r}/{t} results recieved.")
- write_dict_to_csv(results, "test2.csv")
-
-if __name__ == "__main__":
+import random
+import sys
+import os
+import time
+import csv
+from timeit import default_timer as timer
+from multiprocessing import Pool
+
+# import cascade
+sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
+
+from cascade.dataflow.dataflow import Event, EventResult, InitClass, InvokeMethod, OpNode
+from cascade.runtime.flink_runtime import FlinkClientSync, FlinkOperator, FlinkRuntime, FlinkStatelessOperator
+from deathstar_hotel_reservation.entities.flight import Flight, flight_op
+from deathstar_hotel_reservation.entities.hotel import Geo, Hotel, Rate, hotel_op
+from deathstar_hotel_reservation.entities.recommendation import Recommendation, recommend_op
+from deathstar_hotel_reservation.entities.search import Search, search_op
+from deathstar_hotel_reservation.entities.user import User, user_op
+import pandas as pd
+
+
+class DeathstarDemo():
+ def __init__(self):
+ self.init_user = OpNode(User, InitClass(), read_key_from="user_id")
+ self.init_hotel = OpNode(Hotel, InitClass(), read_key_from="key")
+ self.init_flight = OpNode(Flight, InitClass(), read_key_from="id")
+
+ def init_runtime(self, runtime, **kwargs):
+ self.runtime = runtime
+ self.runtime.init(**kwargs)
+ self.runtime.add_operator(hotel_op)
+ self.runtime.add_operator(flight_op)
+ self.runtime.add_operator(user_op)
+ # self.runtime.add_stateless_operator(search_op)
+ # self.runtime.add_stateless_operator(recommend_op)
+
+
+ def populate(self):
+ # Create locations & rates for hotels
+ geos = []
+ geos.append(Geo(37.7867, 0))
+ geos.append(Geo(37.7854, -122.4005))
+ geos.append(Geo(37.7867, -122.4071))
+ geos.append(Geo(37.7936, -122.3930))
+ geos.append(Geo(37.7831, -122.4181))
+ geos.append(Geo(37.7863, -122.4015))
+
+ for i in range(6, 100):
+ lat: float = 37.7835 + i / 500.0 * 3
+ lon: float = -122.41 + i / 500.0 * 4
+ geos.append(Geo(lat, lon))
+
+ rates = {}
+ rates[1] = Rate(1, "RACK",
+ "2015-04-09",
+ "2015-04-10",
+ { "BookableRate": 190.0,
+ "Code": "KNG",
+ "RoomDescription": "King sized bed",
+ "TotalRate": 109.0,
+ "TotalRateInclusive": 123.17})
+
+ rates[2] = Rate(2, "RACK",
+ "2015-04-09",
+ "2015-04-10",
+ { "BookableRate": 139.0,
+ "Code": "QN",
+ "RoomDescription": "Queen sized bed",
+ "TotalRate": 139.0,
+ "TotalRateInclusive": 153.09})
+
+ rates[3] = Rate(3, "RACK",
+ "2015-04-09",
+ "2015-04-10",
+ { "BookableRate": 109.0,
+ "Code": "KNG",
+ "RoomDescription": "King sized bed",
+ "TotalRate": 109.0,
+ "TotalRateInclusive": 123.17})
+
+ for i in range(4, 80):
+ if i % 3 == 0:
+ hotel_id = i
+ end_date = "2015-04-"
+ rate = 109.0
+ rate_inc = 123.17
+ if i % 2 == 0:
+ end_date += '17'
+ else:
+ end_date += '24'
+ if i % 5 == 1:
+ rate = 120.0
+ rate_inc = 140.0
+ elif i % 5 == 2:
+ rate = 124.0
+ rate_inc = 144.0
+ elif i % 5 == 3:
+ rate = 132.0
+ rate_inc = 158.0
+ elif i % 5 == 4:
+ rate = 232.0
+ rate_inc = 258.0
+
+ rates[hotel_id] = Rate(i, "RACK",
+ "2015-04-09",
+ end_date,
+ { "BookableRate": rate,
+ "Code": "KNG",
+ "RoomDescription": "King sized bed",
+ "TotalRate": rate,
+ "TotalRateInclusive": rate_inc})
+
+ # we don't create recommendations, because it doesn't really
+ # correspond to an entity
+ prices = []
+
+ prices.append(150.00)
+ prices.append(120.00)
+ prices.append(190.00)
+ prices.append(160.00)
+ prices.append(140.00)
+ prices.append(200.00)
+
+ for i in range(6, 100):
+ price = 179.00
+ if i % 3 == 0:
+ if i % 5 == 0:
+ price = 123.17
+ elif i % 5 == 1:
+ price = 140.00
+ elif i % 5 == 2:
+ price = 144.00
+ elif i % 5 == 3:
+ price = 158.00
+ elif i % 5 == 4:
+ price = 258.00
+
+ prices.append(price)
+
+ # populate users
+ self.users = [User(f"Cornell_{i}", str(i) * 10) for i in range(501)]
+ for user in self.users:
+ event = Event(self.init_user, {"user_id": user.id, "password": user.password}, None)
+ self.runtime.send(event)
+
+ # populate hotels
+ self.hotels: list[Hotel] = []
+ for i in range(100):
+ geo = geos[i]
+ rate = rates[i] if i in rates else []
+ price = prices[i]
+ hotel = Hotel(str(i), 10, geo, rate, price)
+ self.hotels.append(hotel)
+ event = Event(self.init_hotel,
+ {
+ "key": hotel.key,
+ "cap": hotel.cap,
+ "geo": hotel.geo,
+ "rates": hotel.rates,
+ "price": hotel.price
+ }, None)
+ self.runtime.send(event)
+
+ # populate flights
+ self.flights = [Flight(str(i), 10) for i in range(100)]
+ for flight in self.flights[:-1]:
+ event = Event(self.init_flight, {
+ "id": flight.id,
+ "cap": flight.cap
+ }, None)
+ self.runtime.send(event)
+ flight = self.flights[-1]
+ event = Event(self.init_flight, {
+ "id": flight.id,
+ "cap": flight.cap
+ }, None)
+ self.runtime.send(event, flush=True)
+
+def search_hotel():
+ in_date = random.randint(9, 23)
+ out_date = random.randint(in_date + 1, 24)
+
+ if in_date < 10:
+ in_date_str = f"2015-04-0{in_date}"
+ else:
+ in_date_str = f"2015-04-{in_date}"
+ if out_date < 10:
+ out_date_str = f"2015-04-0{out_date}"
+ else:
+ out_date_str = f"2015-04-{out_date}"
+
+ lat = 38.0235 + (random.randint(0, 481) - 240.5) / 1000.0
+ lon = -122.095 + (random.randint(0, 325) - 157.0) / 1000.0
+
+ # We don't really use the in_date, out_date information
+ return Event(search_op.dataflow.entry, {"lat": lat, "lon": lon}, search_op.dataflow)
+
+def recommend(req_param=None):
+ if req_param is None:
+ coin = random.random()
+ if coin < 0.5:
+ req_param = "distance"
+ else:
+ req_param = "price"
+
+ lat = 38.0235 + (random.randint(0, 481) - 240.5) / 1000.0
+ lon = -122.095 + (random.randint(0, 325) - 157.0) / 1000.0
+
+
+ return Event(recommend_op.dataflow.entry, {"requirement": req_param, "lat": lat, "lon": lon}, recommend_op.dataflow)
+
+def user_login(succesfull=True):
+ user_id = random.randint(0, 500)
+ username = f"Cornell_{user_id}"
+ password = str(user_id) * 10 if succesfull else ""
+ return Event(OpNode(User, InvokeMethod("login"), read_key_from="user_key"), {"user_key": username, "password": password}, None)
+
+
+def reserve():
+ hotel_id = random.randint(0, 99)
+ flight_id = random.randint(0, 99)
+
+ user_id = "Cornell_" + str(random.randint(0, 500))
+
+ return Event(
+ user_op.dataflows["order"].entry,
+ {
+ "user_key": user_id,
+ "flight_key": str(flight_id),
+ "hotel_key": str(hotel_id)
+ },
+ user_op.dataflows["order"])
+
+def deathstar_workload_generator():
+ search_ratio = 0.6
+ recommend_ratio = 0.39
+ user_ratio = 0.005
+ reserve_ratio = 0.005
+ c = 0
+ while True:
+ coin = random.random()
+ if coin < search_ratio:
+ yield search_hotel()
+ elif coin < search_ratio + recommend_ratio:
+ yield recommend()
+ elif coin < search_ratio + recommend_ratio + user_ratio:
+ yield user_login()
+ else:
+ yield reserve()
+ c += 1
+
+def reserve_workload_generator():
+ while True:
+ yield reserve()
+
+def user_login_workload_generator():
+ while True:
+ yield user_login()
+
+threads = 1
+messages_per_burst = 10
+sleeps_per_burst = 1
+sleep_time = 0.0085
+seconds_per_burst = 1
+bursts = 50
+
+
+def benchmark_runner(proc_num) -> dict[int, dict]:
+ print(f'Generator: {proc_num} starting')
+ client = FlinkClientSync("deathstar", "ds-out", "localhost:9092", True)
+ deathstar_generator = user_login_workload_generator()
+ start = timer()
+
+ for _ in range(bursts):
+ sec_start = timer()
+
+ # send burst of messages
+ for i in range(messages_per_burst):
+
+ # sleep sometimes between messages
+ if i % (messages_per_burst // sleeps_per_burst) == 0:
+ time.sleep(sleep_time)
+ event = next(deathstar_generator)
+ client.send(event)
+
+ client.flush()
+ sec_end = timer()
+
+ # wait out the second
+ lps = sec_end - sec_start
+ if lps < seconds_per_burst:
+ time.sleep(1 - lps)
+ sec_end2 = timer()
+ print(f'Latency per burst: {sec_end2 - sec_start} ({seconds_per_burst})')
+
+ end = timer()
+ print(f'Average latency per burst: {(end - start) / bursts} ({seconds_per_burst})')
+
+ done = False
+ while not done:
+ done = True
+ for event_id, fut in client._futures.items():
+ result = fut["ret"]
+ if result is None:
+ done = False
+ time.sleep(0.5)
+ break
+ futures = client._futures
+ client.close()
+ return futures
+
+
+def write_dict_to_pkl(futures_dict, filename):
+ """
+ Writes a dictionary of event data to a pickle file.
+
+ Args:
+ futures_dict (dict): A dictionary where each key is an event ID and the value is another dict.
+ filename (str): The name of the pickle file to write to.
+ """
+
+ # Prepare the data for the DataFrame
+ data = []
+ for event_id, event_data in futures_dict.items():
+ ret: EventResult = event_data.get("ret")
+ row = {
+ "event_id": event_id,
+ "sent": str(event_data.get("sent")),
+ "sent_t": event_data.get("sent_t"),
+ "ret": str(event_data.get("ret")),
+ "ret_t": event_data.get("ret_t"),
+ "roundtrip": ret.metadata["roundtrip"] if ret else None,
+ "flink_time": ret.metadata["flink_time"] if ret else None,
+ "deser_times": ret.metadata["deser_times"] if ret else None,
+ "loops": ret.metadata["loops"] if ret else None,
+ "latency": event_data["ret_t"][1] - event_data["sent_t"][1] if ret else None
+ }
+ data.append(row)
+
+ # Create a DataFrame and save it as a pickle file
+ df = pd.DataFrame(data)
+ df.to_pickle(filename)
+
+def main():
+ ds = DeathstarDemo()
+ ds.init_runtime(FlinkRuntime("deathstar", "ds-out", ui_port=8081), bundle_time=5, bundle_size=10)
+ ds.runtime.run(run_async=True)
+ ds.populate()
+
+
+ time.sleep(1)
+ input()
+
+ # with Pool(threads) as p:
+ # results = p.map(benchmark_runner, range(threads))
+
+ # results = {k: v for d in results for k, v in d.items()}
+ results = benchmark_runner(0)
+
+ # pd.DataFrame({"request_id": list(results.keys()),
+ # "timestamp": [res["timestamp"] for res in results.values()],
+ # "op": [res["op"] for res in results.values()]
+ # }).sort_values("timestamp").to_csv(f'{SAVE_DIR}/client_requests.csv', index=False)
+ print(results)
+ t = len(results)
+ r = 0
+ for result in results.values():
+ if result["ret"] is not None:
+ print(result)
+ r += 1
+ print(f"{r}/{t} results recieved.")
+ write_dict_to_pkl(results, "test2.pkl")
+
+if __name__ == "__main__":
main()
\ No newline at end of file
diff --git a/deathstar/demo_python.py b/deathstar_hotel_reservation/demo_python.py
similarity index 97%
rename from deathstar/demo_python.py
rename to deathstar_hotel_reservation/demo_python.py
index 80e687c..1d44ac4 100644
--- a/deathstar/demo_python.py
+++ b/deathstar_hotel_reservation/demo_python.py
@@ -7,7 +7,7 @@
from cascade.runtime.python_runtime import PythonRuntime
-from deathstar.demo import DeathstarDemo, deathstar_workload_generator
+from deathstar_hotel_reservation.demo import DeathstarDemo, deathstar_workload_generator
from timeit import default_timer as timer
import csv
diff --git a/deathstar/entities/__init__.py b/deathstar_hotel_reservation/entities/__init__.py
similarity index 100%
rename from deathstar/entities/__init__.py
rename to deathstar_hotel_reservation/entities/__init__.py
diff --git a/deathstar/entities/flight.py b/deathstar_hotel_reservation/entities/flight.py
similarity index 89%
rename from deathstar/entities/flight.py
rename to deathstar_hotel_reservation/entities/flight.py
index 60af68b..9a70415 100644
--- a/deathstar/entities/flight.py
+++ b/deathstar_hotel_reservation/entities/flight.py
@@ -1,33 +1,32 @@
-from typing import Any
-from cascade.dataflow.dataflow import Operator
-from cascade.dataflow.operator import StatefulOperator
-
-
-class Flight():
- def __init__(self, id: str, cap: int):
- self.id = id
- self.cap = cap
- # self.customers = []
-
- # In order to be deterministic, we don't actually change the capacity
- def reserve(self) -> bool:
- if self.cap <= 0:
- return False
- return True
-
-
-#### COMPILED FUNCTIONS (ORACLE) #####
-
-def reserve_compiled(variable_map: dict[str, Any], state: Flight, key_stack: list[str]) -> Any:
- key_stack.pop()
- if state.cap <= 0:
- return False
- return True
-
-flight_op = StatefulOperator(
- Flight,
- {
- "reserve": reserve_compiled
- },
- {} # no dataflow?
-)
+from typing import Any
+from cascade.dataflow.dataflow import Operator
+from cascade.dataflow.operator import StatefulOperator
+
+
+class Flight():
+ def __init__(self, id: str, cap: int):
+ self.id = id
+ self.cap = cap
+ # self.customers = []
+
+ # In order to be deterministic, we don't actually change the capacity
+ def reserve(self) -> bool:
+ if self.cap <= 0:
+ return False
+ return True
+
+
+#### COMPILED FUNCTIONS (ORACLE) #####
+
+def reserve_compiled(variable_map: dict[str, Any], state: Flight) -> Any:
+ if state.cap <= 0:
+ return False
+ return True
+
+flight_op = StatefulOperator(
+ Flight,
+ {
+ "reserve": reserve_compiled
+ },
+ {} # no dataflow?
+)
diff --git a/deathstar/entities/hotel.py b/deathstar_hotel_reservation/entities/hotel.py
similarity index 80%
rename from deathstar/entities/hotel.py
rename to deathstar_hotel_reservation/entities/hotel.py
index 6689168..923acb6 100644
--- a/deathstar/entities/hotel.py
+++ b/deathstar_hotel_reservation/entities/hotel.py
@@ -1,84 +1,81 @@
-from dataclasses import dataclass
-from typing import Any, Optional
-from cascade.dataflow.operator import StatefulOperator
-from geopy.distance import distance
-
-
-@dataclass
-class Geo():
- lat: float
- lon: float
-
- def distance_km(self, lat: float, lon: float):
- return distance((lat, lon), (self.lat, self.lon)).km
-
-@dataclass
-class Rate():
- key: int
- code: str
- in_date: str
- out_date: str
- room_type: dict
-
- def __key__(self):
- return self.key
-
-# todo: add a linked entity
-# e.g. reviews: list[Review] where Review is an entity
-class Hotel():
- def __init__(self,
- key: str,
- cap: int,
- geo: Geo,
- rates: list[Rate],
- price: float):
- self.key = key
- self.cap = cap
- self.customers = []
- self.rates = rates
- self.geo = geo
- self.price = price
-
- # In order to be deterministic, we don't actually change the capacity
- def reserve(self) -> bool:
- if self.cap < 0:
- return False
- return True
-
- def get_geo(self) -> Geo:
- return self.geo
-
- @staticmethod
- def __all__() -> list['Hotel']:
- pass
-
- def __key__(self) -> int:
- return self.key
-
-
-
-#### COMPILED FUNCTIONS (ORACLE) #####
-
-def reserve_compiled(variable_map: dict[str, Any], state: Hotel, key_stack: list[str]) -> Any:
- key_stack.pop()
- if state.cap <= 0:
- return False
- return True
-
-def get_geo_compiled(variable_map: dict[str, Any], state: Hotel, key_stack: list[str]) -> Any:
- key_stack.pop()
- return state.geo
-
-def get_price_compiled(variable_map: dict[str, Any], state: Hotel, key_stack: list[str]) -> Any:
- key_stack.pop()
- return state.price
-
-hotel_op = StatefulOperator(
- Hotel,
- {
- "reserve": reserve_compiled,
- "get_geo": get_geo_compiled,
- "get_price": get_price_compiled
- },
- {} # no dataflow?
-)
+from dataclasses import dataclass
+from typing import Any
+from cascade.dataflow.operator import StatefulOperator
+from geopy.distance import distance
+
+
+@dataclass
+class Geo():
+ lat: float
+ lon: float
+
+ def distance_km(self, lat: float, lon: float):
+ return distance((lat, lon), (self.lat, self.lon)).km
+
+@dataclass
+class Rate():
+ key: int
+ code: str
+ in_date: str
+ out_date: str
+ room_type: dict
+
+ def __key__(self):
+ return self.key
+
+# todo: add a linked entity
+# e.g. reviews: list[Review] where Review is an entity
+class Hotel():
+ def __init__(self,
+ key: str,
+ cap: int,
+ geo: Geo,
+ rates: list[Rate],
+ price: float):
+ self.key = key
+ self.cap = cap
+ self.customers = []
+ self.rates = rates
+ self.geo = geo
+ self.price = price
+
+ # In order to be deterministic, we don't actually change the capacity
+ def reserve(self) -> bool:
+ if self.cap < 0:
+ return False
+ return True
+
+ def get_geo(self) -> Geo:
+ return self.geo
+
+ @staticmethod
+ def __all__() -> list['Hotel']:
+ pass
+
+ def __key__(self) -> int:
+ return self.key
+
+
+
+#### COMPILED FUNCTIONS (ORACLE) #####
+
+def reserve_compiled(variable_map: dict[str, Any], state: Hotel) -> Any:
+ if state.cap <= 0:
+ return False
+ return True
+
+def get_geo_compiled(variable_map: dict[str, Any], state: Hotel) -> Any:
+ return state.geo
+
+def get_price_compiled(variable_map: dict[str, Any], state: Hotel) -> Any:
+ return state.price
+
+hotel_op = StatefulOperator(
+ Hotel,
+ {
+ "reserve": reserve_compiled,
+ "get_geo": get_geo_compiled,
+ "get_price": get_price_compiled
+ },
+ {} # no dataflow?
+)
diff --git a/deathstar/entities/recommendation.py b/deathstar_hotel_reservation/entities/recommendation.py
similarity index 57%
rename from deathstar/entities/recommendation.py
rename to deathstar_hotel_reservation/entities/recommendation.py
index 7667210..576da60 100644
--- a/deathstar/entities/recommendation.py
+++ b/deathstar_hotel_reservation/entities/recommendation.py
@@ -1,137 +1,129 @@
-from typing import Any, Literal
-from cascade.dataflow.dataflow import CollectNode, DataFlow, Edge, InvokeMethod, OpNode, SelectAllNode
-from cascade.dataflow.operator import StatelessOperator
-from deathstar.entities.hotel import Geo, Hotel, hotel_op
-
-# Stateless
-class Recommendation():
- @staticmethod
- def get_recommendations(requirement: Literal["distance", "price"], lat: float, lon: float) -> list[Hotel]:
- if requirement == "distance":
- distances = [(hotel.geo.distance_km(lat, lon), hotel)
- for hotel in Hotel.__all__()]
- min_dist = min(distances, key=lambda x: x[0])
- res = [hotel for dist, hotel in distances if dist == min_dist]
- elif requirement == "price":
- prices = [(hotel.price, hotel)
- for hotel in Hotel.__all__()]
- min_price = min(prices, key=lambda x: x[0])
- res = [hotel for rate, hotel in prices if rate == min_price]
-
- # todo: raise error on else ...?
- return res
-
-#### COMPILED FUNCTIONS (ORACLE) ####
-
-def get_recs_if_cond(variable_map: dict[str, Any], key_stack: list[str]):
- return variable_map["requirement"] == "distance"
-
-# list comprehension entry
-def get_recs_if_body_0(variable_map: dict[str, Any], key_stack: list[str]):
- hotel_key = key_stack[-1]
- # The body will need the hotel key (actually, couldn't we just take the top of the key stack again?)
- variable_map["hotel_key"] = hotel_key
- # The next node (Hotel.get_geo) will need the hotel key
- key_stack.append(hotel_key)
-
-
-# list comprehension body
-def get_recs_if_body_1(variable_map: dict[str, Any], key_stack: list[str]):
- hotel_geo: Geo = variable_map["hotel_geo"]
- lat, lon = variable_map["lat"], variable_map["lon"]
- dist = hotel_geo.distance_km(lat, lon)
- return (dist, variable_map["hotel_key"])
-
-# after list comprehension
-def get_recs_if_body_2(variable_map: dict[str, Any], key_stack: list[str]):
- distances = variable_map["distances"]
- min_dist = min(distances, key=lambda x: x[0])[0]
- variable_map["res"] = [hotel for dist, hotel in distances if dist == min_dist]
-
-
-def get_recs_elif_cond(variable_map: dict[str, Any], key_stack: list[str]):
- return variable_map["requirement"] == "price"
-
-
-# list comprehension entry
-def get_recs_elif_body_0(variable_map: dict[str, Any], key_stack: list[str]):
- hotel_key = key_stack[-1]
- # The body will need the hotel key (actually, couldn't we just take the top of the key stack again?)
- variable_map["hotel_key"] = hotel_key
- # The next node (Hotel.get_geo) will need the hotel key
- key_stack.append(hotel_key)
-
-
-# list comprehension body
-def get_recs_elif_body_1(variable_map: dict[str, Any], key_stack: list[str]):
- return (variable_map["hotel_price"], variable_map["hotel_key"])
-
-# after list comprehension
-def get_recs_elif_body_2(variable_map: dict[str, Any], key_stack: list[str]):
- prices = variable_map["prices"]
- min_price = min(prices, key=lambda x: x[0])[0]
- variable_map["res"] = [hotel for price, hotel in prices if price == min_price]
-
-
-
-# a future optimization might instead duplicate this piece of code over the two
-# branches, in order to reduce the number of splits by one
-def get_recs_final(variable_map: dict[str, Any], key_stack: list[str]):
- return variable_map["res"]
-
-
-recommend_op = StatelessOperator({
- "get_recs_if_cond": get_recs_if_cond,
- "get_recs_if_body_0": get_recs_if_body_0,
- "get_recs_if_body_1": get_recs_if_body_1,
- "get_recs_if_body_2": get_recs_if_body_2,
- "get_recs_elif_cond": get_recs_elif_cond,
- "get_recs_elif_body_0": get_recs_elif_body_0,
- "get_recs_elif_body_1": get_recs_elif_body_1,
- "get_recs_elif_body_2": get_recs_elif_body_2,
- "get_recs_final": get_recs_final,
-}, None)
-
-df = DataFlow("get_recommendations")
-n1 = OpNode(recommend_op, InvokeMethod("get_recs_if_cond"), is_conditional=True)
-n2 = OpNode(recommend_op, InvokeMethod("get_recs_if_body_0"))
-n3 = OpNode(hotel_op, InvokeMethod("get_geo"), assign_result_to="hotel_geo")
-n4 = OpNode(recommend_op, InvokeMethod("get_recs_if_body_1"), assign_result_to="distance")
-n5 = CollectNode("distances", "distance")
-n6 = OpNode(recommend_op, InvokeMethod("get_recs_if_body_2"))
-ns1 = SelectAllNode(Hotel, n5)
-
-n7 = OpNode(recommend_op, InvokeMethod("get_recs_elif_cond"), is_conditional=True)
-n8 = OpNode(recommend_op, InvokeMethod("get_recs_elif_body_0"))
-n9 = OpNode(hotel_op, InvokeMethod("get_price"), assign_result_to="hotel_price")
-n10 = OpNode(recommend_op, InvokeMethod("get_recs_elif_body_1"), assign_result_to="price")
-n11 = CollectNode("prices", "price")
-n12 = OpNode(recommend_op, InvokeMethod("get_recs_elif_body_2"))
-ns2 = SelectAllNode(Hotel, n11)
-
-
-n13 = OpNode(recommend_op, InvokeMethod("get_recs_final"))
-
-df.add_edge(Edge(n1, ns1, if_conditional=True))
-df.add_edge(Edge(n1, n7, if_conditional=False))
-df.add_edge(Edge(n7, ns2, if_conditional=True))
-df.add_edge(Edge(n7, n13, if_conditional=False))
-
-# if branch
-df.add_edge(Edge(ns1, n2))
-df.add_edge(Edge(n2, n3))
-df.add_edge(Edge(n3, n4))
-df.add_edge(Edge(n4, n5))
-df.add_edge(Edge(n5, n6))
-df.add_edge(Edge(n6, n13))
-
-# elif branch
-df.add_edge(Edge(ns2, n8))
-df.add_edge(Edge(n8, n9))
-df.add_edge(Edge(n9, n10))
-df.add_edge(Edge(n10, n11))
-df.add_edge(Edge(n11, n12))
-df.add_edge(Edge(n12, n13))
-
-df.entry = n1
+from typing import Any, Literal
+from cascade.dataflow.dataflow import CollectNode, DataFlow, Edge, InvokeMethod, OpNode, SelectAllNode, StatelessOpNode
+from cascade.dataflow.operator import StatelessOperator
+from deathstar_hotel_reservation.entities.hotel import Geo, Hotel
+
+# Stateless
+class Recommendation():
+ @staticmethod
+ def get_recommendations(requirement: Literal["distance", "price"], lat: float, lon: float) -> list[Hotel]:
+ if requirement == "distance":
+ distances = [(hotel.geo.distance_km(lat, lon), hotel)
+ for hotel in Hotel.__all__()]
+ min_dist = min(distances, key=lambda x: x[0])
+ res = [hotel for dist, hotel in distances if dist == min_dist]
+ elif requirement == "price":
+ prices = [(hotel.price, hotel)
+ for hotel in Hotel.__all__()]
+ min_price = min(prices, key=lambda x: x[0])
+ res = [hotel for rate, hotel in prices if rate == min_price]
+
+ # todo: raise error on else ...?
+ return res
+
+#### COMPILED FUNCTIONS (ORACLE) ####
+
+def get_recs_if_cond(variable_map: dict[str, Any]):
+ return variable_map["requirement"] == "distance"
+
+# list comprehension entry
+def get_recs_if_body_0(variable_map: dict[str, Any]):
+ pass
+
+
+# list comprehension body
+def get_recs_if_body_1(variable_map: dict[str, Any]):
+ hotel_geo: Geo = variable_map["hotel_geo"]
+ lat, lon = variable_map["lat"], variable_map["lon"]
+ dist = hotel_geo.distance_km(lat, lon)
+ return (dist, variable_map["hotel_key"])
+
+# after list comprehension
+def get_recs_if_body_2(variable_map: dict[str, Any]):
+ distances = variable_map["distances"]
+ min_dist = min(distances, key=lambda x: x[0])[0]
+ variable_map["res"] = [hotel for dist, hotel in distances if dist == min_dist]
+
+
+def get_recs_elif_cond(variable_map: dict[str, Any]):
+ return variable_map["requirement"] == "price"
+
+
+# list comprehension entry
+def get_recs_elif_body_0(variable_map: dict[str, Any]):
+ pass
+
+
+# list comprehension body
+def get_recs_elif_body_1(variable_map: dict[str, Any]):
+ return (variable_map["hotel_price"], variable_map["hotel_key"])
+
+# after list comprehension
+def get_recs_elif_body_2(variable_map: dict[str, Any]):
+ prices = variable_map["prices"]
+ min_price = min(prices, key=lambda x: x[0])[0]
+ variable_map["res"] = [hotel for price, hotel in prices if price == min_price]
+
+
+
+# a future optimization might instead duplicate this piece of code over the two
+# branches, in order to reduce the number of splits by one
+def get_recs_final(variable_map: dict[str, Any]):
+ return variable_map["res"]
+
+
+recommend_op = StatelessOperator({
+ "get_recs_if_cond": get_recs_if_cond,
+ "get_recs_if_body_0": get_recs_if_body_0,
+ "get_recs_if_body_1": get_recs_if_body_1,
+ "get_recs_if_body_2": get_recs_if_body_2,
+ "get_recs_elif_cond": get_recs_elif_cond,
+ "get_recs_elif_body_0": get_recs_elif_body_0,
+ "get_recs_elif_body_1": get_recs_elif_body_1,
+ "get_recs_elif_body_2": get_recs_elif_body_2,
+ "get_recs_final": get_recs_final,
+}, None)
+
+df = DataFlow("get_recommendations")
+n1 = StatelessOpNode(recommend_op, InvokeMethod("get_recs_if_cond"), is_conditional=True)
+n2 = StatelessOpNode(recommend_op, InvokeMethod("get_recs_if_body_0"))
+n3 = OpNode(Hotel, InvokeMethod("get_geo"), assign_result_to="hotel_geo", read_key_from="hotel_key")
+n4 = StatelessOpNode(recommend_op, InvokeMethod("get_recs_if_body_1"), assign_result_to="distance")
+n5 = CollectNode("distances", "distance")
+n6 = StatelessOpNode(recommend_op, InvokeMethod("get_recs_if_body_2"))
+ns1 = SelectAllNode(Hotel, n5, assign_key_to="hotel_key")
+
+n7 = StatelessOpNode(recommend_op, InvokeMethod("get_recs_elif_cond"), is_conditional=True)
+n8 = StatelessOpNode(recommend_op, InvokeMethod("get_recs_elif_body_0"))
+n9 = OpNode(Hotel, InvokeMethod("get_price"), assign_result_to="hotel_price", read_key_from="hotel_key")
+n10 = StatelessOpNode(recommend_op, InvokeMethod("get_recs_elif_body_1"), assign_result_to="price")
+n11 = CollectNode("prices", "price")
+n12 = StatelessOpNode(recommend_op, InvokeMethod("get_recs_elif_body_2"))
+ns2 = SelectAllNode(Hotel, n11, assign_key_to="hotel_key")
+
+
+n13 = StatelessOpNode(recommend_op, InvokeMethod("get_recs_final"))
+
+df.add_edge(Edge(n1, ns1, if_conditional=True))
+df.add_edge(Edge(n1, n7, if_conditional=False))
+df.add_edge(Edge(n7, ns2, if_conditional=True))
+df.add_edge(Edge(n7, n13, if_conditional=False))
+
+# if branch
+df.add_edge(Edge(ns1, n2))
+df.add_edge(Edge(n2, n3))
+df.add_edge(Edge(n3, n4))
+df.add_edge(Edge(n4, n5))
+df.add_edge(Edge(n5, n6))
+df.add_edge(Edge(n6, n13))
+
+# elif branch
+df.add_edge(Edge(ns2, n8))
+df.add_edge(Edge(n8, n9))
+df.add_edge(Edge(n9, n10))
+df.add_edge(Edge(n10, n11))
+df.add_edge(Edge(n11, n12))
+df.add_edge(Edge(n12, n13))
+
+df.entry = n1
recommend_op.dataflow = df
\ No newline at end of file
diff --git a/deathstar/entities/search.py b/deathstar_hotel_reservation/entities/search.py
similarity index 55%
rename from deathstar/entities/search.py
rename to deathstar_hotel_reservation/entities/search.py
index a2782d2..abc5895 100644
--- a/deathstar/entities/search.py
+++ b/deathstar_hotel_reservation/entities/search.py
@@ -1,85 +1,77 @@
-from typing import Any
-from cascade.dataflow.dataflow import CollectNode, DataFlow, Edge, InvokeMethod, OpNode, SelectAllNode
-from cascade.dataflow.operator import StatelessOperator
-from deathstar.entities.hotel import Geo, Hotel, hotel_op
-
-# Stateless
-class Search():
- # Get the 5 nearest hotels
- @staticmethod
- def nearby(lat: float, lon: float, in_date: int, out_date: int):
- distances = [
- (dist, hotel)
- for hotel in Hotel.__all__()
- if (dist := hotel.geo.distance_km(lat, lon)) < 10]
- hotels = [hotel for dist, hotel in sorted(distances)[:5]]
- return hotels
-
-
-#### COMPILED FUNCTIONS (ORACLE) #####
-
-
-
-# predicate 1
-def search_nearby_compiled_0(variable_map: dict[str, Any], key_stack: list[str]):
- # We assume that the top of the key stack is the hotel key.
- # This assumption holds if the node before this one is a correctly
- # configure SelectAllNode.
-
- hotel_key = key_stack[-1]
- # The body will need the hotel key (actually, couldn't we just take the top of the key stack again?)
- variable_map["hotel_key"] = hotel_key
- # The next node (Hotel.get_geo) will need the hotel key
- key_stack.append(hotel_key)
-
-# predicate 2
-def search_nearby_compiled_1(variable_map: dict[str, Any], key_stack: list[str]):
- hotel_geo: Geo = variable_map["hotel_geo"]
- lat, lon = variable_map["lat"], variable_map["lon"]
- dist = hotel_geo.distance_km(lat, lon)
- variable_map["dist"] = dist
- return dist < 10
-
-
-# body
-def search_nearby_compiled_2(variable_map: dict[str, Any], key_stack: list[str]):
- return (variable_map["dist"], variable_map["hotel_key"])
-
-# next line
-def search_nearby_compiled_3(variable_map: dict[str, Any], key_stack: list[str]):
- distances = variable_map["distances"]
- hotels = [hotel for dist, hotel in sorted(distances)[:5]]
- return hotels
-
-
-search_op = StatelessOperator({
- "search_nearby_compiled_0": search_nearby_compiled_0,
- "search_nearby_compiled_1": search_nearby_compiled_1,
- "search_nearby_compiled_2": search_nearby_compiled_2,
- "search_nearby_compiled_3": search_nearby_compiled_3,
-}, None)
-
-df = DataFlow("search_nearby")
-n1 = OpNode(search_op, InvokeMethod("search_nearby_compiled_0"))
-n2 = OpNode(hotel_op, InvokeMethod("get_geo"), assign_result_to="hotel_geo")
-n3 = OpNode(search_op, InvokeMethod("search_nearby_compiled_1"), is_conditional=True)
-n4 = OpNode(search_op, InvokeMethod("search_nearby_compiled_2"), assign_result_to="search_body")
-n5 = CollectNode("distances", "search_body")
-n0 = SelectAllNode(Hotel, n5)
-
-n6 = OpNode(search_op, InvokeMethod("search_nearby_compiled_3"))
-
-df.add_edge(Edge(n0, n1))
-df.add_edge(Edge(n1, n2))
-df.add_edge(Edge(n2, n3))
-
-# if true make the body
-df.add_edge(Edge(n3, n4, if_conditional=True))
-df.add_edge(Edge(n4, n5))
-# if false skip past
-df.add_edge(Edge(n3, n5, if_conditional=False))
-
-df.add_edge(Edge(n5, n6))
-
-df.entry = n0
+from typing import Any
+from cascade.dataflow.dataflow import CollectNode, DataFlow, Edge, InvokeMethod, OpNode, SelectAllNode, StatelessOpNode
+from cascade.dataflow.operator import StatelessOperator
+from deathstar_hotel_reservation.entities.hotel import Geo, Hotel, hotel_op
+
+# Stateless
+class Search():
+ # Get the 5 nearest hotels
+ @staticmethod
+ def nearby(lat: float, lon: float, in_date: int, out_date: int):
+ distances = [
+ (dist, hotel)
+ for hotel in Hotel.__all__()
+ if (dist := hotel.geo.distance_km(lat, lon)) < 10]
+ hotels = [hotel for dist, hotel in sorted(distances)[:5]]
+ return hotels
+
+
+#### COMPILED FUNCTIONS (ORACLE) #####
+
+
+
+# predicate 1
+def search_nearby_compiled_0(variable_map: dict[str, Any]):
+ pass
+
+# predicate 2
+def search_nearby_compiled_1(variable_map: dict[str, Any]):
+ hotel_geo: Geo = variable_map["hotel_geo"]
+ lat, lon = variable_map["lat"], variable_map["lon"]
+ dist = hotel_geo.distance_km(lat, lon)
+ variable_map["dist"] = dist
+ return dist < 10
+
+
+# body
+def search_nearby_compiled_2(variable_map: dict[str, Any]):
+ return (variable_map["dist"], variable_map["hotel_key"])
+
+# next line
+def search_nearby_compiled_3(variable_map: dict[str, Any]):
+ distances = variable_map["distances"]
+ hotels = [hotel for dist, hotel in sorted(distances)[:5]]
+ return hotels
+
+
+search_op = StatelessOperator({
+ "search_nearby_compiled_0": search_nearby_compiled_0,
+ "search_nearby_compiled_1": search_nearby_compiled_1,
+ "search_nearby_compiled_2": search_nearby_compiled_2,
+ "search_nearby_compiled_3": search_nearby_compiled_3,
+}, None)
+
+df = DataFlow("search_nearby")
+n1 = StatelessOpNode(search_op, InvokeMethod("search_nearby_compiled_0"))
+n2 = OpNode(Hotel, InvokeMethod("get_geo"), assign_result_to="hotel_geo", read_key_from="hotel_key")
+n3 = StatelessOpNode(search_op, InvokeMethod("search_nearby_compiled_1"), is_conditional=True)
+n4 = StatelessOpNode(search_op, InvokeMethod("search_nearby_compiled_2"), assign_result_to="search_body")
+n5 = CollectNode("distances", "search_body")
+n0 = SelectAllNode(Hotel, n5, assign_key_to="hotel_key")
+
+n6 = StatelessOpNode(search_op, InvokeMethod("search_nearby_compiled_3"))
+
+df.add_edge(Edge(n0, n1))
+df.add_edge(Edge(n1, n2))
+df.add_edge(Edge(n2, n3))
+
+# if true make the body
+df.add_edge(Edge(n3, n4, if_conditional=True))
+df.add_edge(Edge(n4, n5))
+# if false skip past
+df.add_edge(Edge(n3, n5, if_conditional=False))
+
+df.add_edge(Edge(n5, n6))
+
+df.entry = n0
search_op.dataflow = df
\ No newline at end of file
diff --git a/deathstar_hotel_reservation/entities/user.py b/deathstar_hotel_reservation/entities/user.py
new file mode 100644
index 0000000..dba7567
--- /dev/null
+++ b/deathstar_hotel_reservation/entities/user.py
@@ -0,0 +1,105 @@
+from typing import Any
+from cascade.dataflow.dataflow import CollectNode, CollectTarget, DataFlow, Edge, InvokeMethod, OpNode
+from cascade.dataflow.operator import StatefulOperator
+from deathstar_hotel_reservation.entities.flight import Flight, flight_op
+from deathstar_hotel_reservation.entities.hotel import Hotel, hotel_op
+
+
+class User():
+ def __init__(self, user_id: str, password: str):
+ self.id = user_id
+ self.password = password
+
+ def check(self, password):
+ return self.password == password
+
+ def order(self, flight: Flight, hotel: Hotel):
+ if hotel.reserve() and flight.reserve():
+ return True
+ else:
+ return False
+
+#### COMPILED FUNCTIONS (ORACLE) #####
+
+def check_compiled(variable_map: dict[str, Any], state: User) -> Any:
+ return state.password == variable_map["password"]
+
+def order_compiled_entry_0(variable_map: dict[str, Any], state: User) -> Any:
+ pass
+
+def order_compiled_entry_1(variable_map: dict[str, Any], state: User) -> Any:
+ pass
+
+def order_compiled_if_cond(variable_map: dict[str, Any], state: User) -> Any:
+ return variable_map["hotel_reserve"] and variable_map["flight_reserve"]
+
+def order_compiled_if_cond_parallel(variable_map: dict[str, Any], state: User) -> Any:
+ return variable_map["reserves"][0] and variable_map["reserves"][1]
+
+def order_compiled_if_body(variable_map: dict[str, Any], state: User) -> Any:
+ return True
+
+def order_compiled_else_body(variable_map: dict[str, Any], state: User) -> Any:
+ return False
+
+user_op = StatefulOperator(
+ User,
+ {
+ "login": check_compiled,
+ "order_compiled_entry_0": order_compiled_entry_0,
+ "order_compiled_entry_1": order_compiled_entry_1,
+ # "order_compiled_if_cond": order_compiled_if_cond,
+ "order_compiled_if_cond": order_compiled_if_cond_parallel,
+ "order_compiled_if_body": order_compiled_if_body,
+ "order_compiled_else_body": order_compiled_else_body
+ },
+ {}
+)
+
+# For now, the dataflow will be serial instead of parallel. Future optimizations
+# will try to automatically parallelize this.
+# There is also no user entry (this could also be an optimization)
+def df_serial():
+ df = DataFlow("user_order")
+ n0 = OpNode(User, InvokeMethod("order_compiled_entry_0"), read_key_from="user_key")
+ n1 = OpNode(Hotel, InvokeMethod("reserve"), assign_result_to="hotel_reserve", read_key_from="hotel_key")
+ n2 = OpNode(User, InvokeMethod("order_compiled_entry_1"), read_key_from="user_key")
+ n3 = OpNode(Flight, InvokeMethod("reserve"), assign_result_to="flight_reserve", read_key_from="flight_key")
+ n4 = OpNode(User, InvokeMethod("order_compiled_if_cond"), is_conditional=True, read_key_from="user_key")
+ n5 = OpNode(User, InvokeMethod("order_compiled_if_body"), read_key_from="user_key")
+ n6 = OpNode(User, InvokeMethod("order_compiled_else_body"), read_key_from="user_key")
+
+ df.add_edge(Edge(n0, n1))
+ df.add_edge(Edge(n1, n2))
+ df.add_edge(Edge(n2, n3))
+ df.add_edge(Edge(n3, n4))
+ df.add_edge(Edge(n4, n5, if_conditional=True))
+ df.add_edge(Edge(n4, n6, if_conditional=False))
+
+ df.entry = n0
+ return df
+
+
+# PARALLEL DATAFLOW
+def df_parallel():
+ df = DataFlow("user_order")
+ n0 = OpNode(User, InvokeMethod("order_compiled_entry_0"), read_key_from="user_key")
+ ct = CollectNode(assign_result_to="reserves", read_results_from="reserve")
+ n1 = OpNode(Hotel, InvokeMethod("reserve"), assign_result_to="reserve", read_key_from="hotel_key", collect_target=CollectTarget(ct, 2, 0))
+ n3 = OpNode(Flight, InvokeMethod("reserve"), assign_result_to="reserve", read_key_from="flight_key", collect_target=CollectTarget(ct, 2, 1))
+ n4 = OpNode(User, InvokeMethod("order_compiled_if_cond"), is_conditional=True, read_key_from="user_key")
+ n5 = OpNode(User, InvokeMethod("order_compiled_if_body"), read_key_from="user_key")
+ n6 = OpNode(User, InvokeMethod("order_compiled_else_body"), read_key_from="user_key")
+
+ df.add_edge(Edge(n0, n1))
+ df.add_edge(Edge(n0, n3))
+ df.add_edge(Edge(n1, ct))
+ df.add_edge(Edge(n3, ct))
+ df.add_edge(Edge(ct, n4))
+ df.add_edge(Edge(n4, n5, if_conditional=True))
+ df.add_edge(Edge(n4, n6, if_conditional=False))
+
+ df.entry = n0
+ return df
+
+user_op.dataflows["order"] = df_serial()
diff --git a/deathstar/test_demo.py b/deathstar_hotel_reservation/test_demo.py
similarity index 91%
rename from deathstar/test_demo.py
rename to deathstar_hotel_reservation/test_demo.py
index a3afd4a..dea227f 100644
--- a/deathstar/test_demo.py
+++ b/deathstar_hotel_reservation/test_demo.py
@@ -1,100 +1,100 @@
-
-import os
-import sys
-
-# import cascade
-sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
-
-from cascade.runtime.python_runtime import PythonClientSync, PythonRuntime
-from cascade.runtime.flink_runtime import FlinkClientSync, FlinkRuntime
-from deathstar.demo import DeathstarDemo, recommend, reserve, search_hotel, user_login
-import time
-import pytest
-
-@pytest.mark.integration
-def test_deathstar_demo():
- ds = DeathstarDemo()
- ds.init_runtime(FlinkRuntime("deathstardemo-test", "dsd-out"))
- ds.runtime.run(run_async=True)
- print("Populating, press enter to go to the next step when done")
- ds.populate()
-
- client = FlinkClientSync("deathstardemo-test", "dsd-out")
- input()
- print("testing user login")
- event = user_login()
- client.send(event)
-
- input()
- print("testing reserve")
- event = reserve()
- client.send(event)
-
- input()
- print("testing search")
- event = search_hotel()
- client.send(event)
-
- input()
- print("testing recommend (distance)")
- time.sleep(0.5)
- event = recommend(req_param="distance")
- client.send(event)
-
- input()
- print("testing recommend (price)")
- time.sleep(0.5)
- event = recommend(req_param="price")
- client.send(event)
-
- print(client._futures)
- input()
- print("done!")
- print(client._futures)
-
-def test_deathstar_demo_python():
- ds = DeathstarDemo()
- ds.init_runtime(PythonRuntime())
- ds.runtime.run()
- print("Populating, press enter to go to the next step when done")
- ds.populate()
-
- time.sleep(2)
-
- client = PythonClientSync(ds.runtime)
- print("testing user login")
- event = user_login()
- result = client.send(event)
- assert result == True
- event = user_login(succesfull=False)
- result = client.send(event)
- assert result == False
-
- print("testing reserve")
- event = reserve()
- result = client.send(event)
- assert result == True
-
- return
- print("testing search")
- event = search_hotel()
- result = client.send(event)
- print(result)
-
- print("testing recommend (distance)")
- time.sleep(0.5)
- event = recommend(req_param="distance")
- result = client.send(event)
- print(result)
-
- print("testing recommend (price)")
- time.sleep(0.5)
- event = recommend(req_param="price")
- result = client.send(event)
- print(result)
-
- print("done!")
-
-
-if __name__ == "__main__":
+
+import os
+import sys
+
+# import cascade
+sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
+
+from cascade.runtime.python_runtime import PythonClientSync, PythonRuntime
+from cascade.runtime.flink_runtime import FlinkClientSync, FlinkRuntime
+from deathstar_hotel_reservation.demo import DeathstarDemo, recommend, reserve, search_hotel, user_login
+import time
+import pytest
+
+@pytest.mark.integration
+def test_deathstar_demo():
+ ds = DeathstarDemo()
+ ds.init_runtime(FlinkRuntime("deathstardemo-test", "dsd-out"))
+ ds.runtime.run(run_async=True)
+ print("Populating, press enter to go to the next step when done")
+ ds.populate()
+
+ client = FlinkClientSync("deathstardemo-test", "dsd-out")
+ input()
+ print("testing user login")
+ event = user_login()
+ client.send(event)
+
+ input()
+ print("testing reserve")
+ event = reserve()
+ client.send(event)
+
+ input()
+ print("testing search")
+ event = search_hotel()
+ client.send(event)
+
+ input()
+ print("testing recommend (distance)")
+ time.sleep(0.5)
+ event = recommend(req_param="distance")
+ client.send(event)
+
+ input()
+ print("testing recommend (price)")
+ time.sleep(0.5)
+ event = recommend(req_param="price")
+ client.send(event)
+
+ print(client._futures)
+ input()
+ print("done!")
+ print(client._futures)
+
+def test_deathstar_demo_python():
+ ds = DeathstarDemo()
+ ds.init_runtime(PythonRuntime())
+ ds.runtime.run()
+ print("Populating, press enter to go to the next step when done")
+ ds.populate()
+
+ time.sleep(0.1)
+
+ client = PythonClientSync(ds.runtime)
+ print("testing user login")
+ event = user_login()
+ result = client.send(event)
+ assert result == True
+ event = user_login(succesfull=False)
+ result = client.send(event)
+ assert result == False
+
+ print("testing reserve")
+ event = reserve()
+ result = client.send(event)
+ assert result == True
+
+ return
+ print("testing search")
+ event = search_hotel()
+ result = client.send(event)
+ print(result)
+
+ print("testing recommend (distance)")
+ time.sleep(0.5)
+ event = recommend(req_param="distance")
+ result = client.send(event)
+ print(result)
+
+ print("testing recommend (price)")
+ time.sleep(0.5)
+ event = recommend(req_param="price")
+ result = client.send(event)
+ print(result)
+
+ print("done!")
+
+
+if __name__ == "__main__":
test_deathstar_demo()
\ No newline at end of file
diff --git a/deathstar_movie_review/__init__.py b/deathstar_movie_review/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/deathstar_movie_review/demo.py b/deathstar_movie_review/demo.py
new file mode 100644
index 0000000..6e546e7
--- /dev/null
+++ b/deathstar_movie_review/demo.py
@@ -0,0 +1,224 @@
+import hashlib
+import uuid
+
+from .movie_data import movie_data
+from .workload_data import movie_titles, charset
+import random
+from timeit import default_timer as timer
+import sys
+import os
+
+# import cascade
+sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
+
+from cascade.dataflow.dataflow import Event, EventResult, InitClass, OpNode
+from cascade.runtime.flink_runtime import FlinkClientSync, FlinkRuntime
+from cascade.dataflow.optimization.dead_node_elim import dead_node_elimination
+
+from .entities.user import user_op, User
+from .entities.compose_review import compose_review_op
+from .entities.frontend import frontend_op, text_op, unique_id_op
+from .entities.movie import MovieInfo, movie_id_op, movie_info_op, plot_op, Plot, MovieId
+
+import time
+import pandas as pd
+
+def populate_user(client: FlinkRuntime):
+ init_user = OpNode(User, InitClass(), read_key_from="username")
+ for i in range(1000):
+ user_id = f'user{i}'
+ username = f'username_{i}'
+ password = f'password_{i}'
+ hasher = hashlib.new('sha512')
+ salt = uuid.uuid1().bytes
+ hasher.update(password.encode())
+ hasher.update(salt)
+
+ password_hash = hasher.hexdigest()
+
+ user_data = {
+ "userId": user_id,
+ "FirstName": "firstname",
+ "LastName": "lastname",
+ "Username": username,
+ "Password": password_hash,
+ "Salt": salt
+ }
+ event = Event(init_user, {"username": username, "user_data": user_data}, None)
+ client.send(event)
+
+
+def populate_movie(client: FlinkRuntime):
+ init_movie_info = OpNode(MovieInfo, InitClass(), read_key_from="movie_id")
+ init_plot = OpNode(Plot, InitClass(), read_key_from="movie_id")
+ init_movie_id = OpNode(MovieId, InitClass(), read_key_from="title")
+
+ for movie in movie_data:
+ movie_id = movie["MovieId"]
+
+ # movie info -> write `movie`
+ event = Event(init_movie_info, {"movie_id": movie_id, "info": movie}, None)
+ client.send(event)
+
+ # plot -> write "plot"
+ event = Event(init_plot, {"movie_id": movie_id, "plot": "plot"}, None)
+ client.send(event)
+
+ # movie_id_op -> register movie id
+ event = Event(init_movie_id, {"title": movie["Title"], "movie_id": movie_id}, None)
+ client.send(event)
+
+
+def compose_review(req_id):
+ user_index = random.randint(0, 999)
+ username = f"username_{user_index}"
+ password = f"password_{user_index}"
+ title = random.choice(movie_titles)
+ rating = random.randint(0, 10)
+ text = ''.join(random.choice(charset) for _ in range(256))
+
+ return frontend_op.dataflow.generate_event({
+ "review": req_id,
+ "user": username,
+ "title": title,
+ "rating": rating,
+ "text": text
+ })
+
+def deathstar_workload_generator():
+ c = 1
+ while True:
+ yield compose_review(c)
+ c += 1
+
+threads = 1
+messages_per_burst = 10
+sleeps_per_burst = 10
+sleep_time = 0.08 #0.0085
+seconds_per_burst = 1
+bursts = 100
+
+
+def benchmark_runner(proc_num) -> dict[int, dict]:
+ print(f'Generator: {proc_num} starting')
+ client = FlinkClientSync("ds-movie-in", "ds-movie-out")
+ deathstar_generator = deathstar_workload_generator()
+ start = timer()
+
+ for _ in range(bursts):
+ sec_start = timer()
+
+ # send burst of messages
+ for i in range(messages_per_burst):
+
+ # sleep sometimes between messages
+ if i % (messages_per_burst // sleeps_per_burst) == 0:
+ time.sleep(sleep_time)
+ event = next(deathstar_generator)
+ client.send(event)
+
+ client.flush()
+ sec_end = timer()
+
+ # wait out the second
+ lps = sec_end - sec_start
+ if lps < seconds_per_burst:
+ time.sleep(1 - lps)
+ sec_end2 = timer()
+ print(f'Latency per burst: {sec_end2 - sec_start} ({seconds_per_burst})')
+
+ end = timer()
+ print(f'Average latency per burst: {(end - start) / bursts} ({seconds_per_burst})')
+
+ done = False
+ while not done:
+ done = True
+ for event_id, fut in client._futures.items():
+ result = fut["ret"]
+ if result is None:
+ done = False
+ time.sleep(0.5)
+ break
+ futures = client._futures
+ client.close()
+ return futures
+
+
+def write_dict_to_pkl(futures_dict, filename):
+ """
+ Writes a dictionary of event data to a pickle file.
+
+ Args:
+ futures_dict (dict): A dictionary where each key is an event ID and the value is another dict.
+ filename (str): The name of the pickle file to write to.
+ """
+
+ # Prepare the data for the DataFrame
+ data = []
+ for event_id, event_data in futures_dict.items():
+ ret: EventResult = event_data.get("ret")
+ row = {
+ "event_id": event_id,
+ "sent": str(event_data.get("sent")),
+ "sent_t": event_data.get("sent_t"),
+ "ret": str(event_data.get("ret")),
+ "ret_t": event_data.get("ret_t"),
+ "roundtrip": ret.metadata["roundtrip"] if ret else None,
+ "flink_time": ret.metadata["flink_time"] if ret else None,
+ "deser_times": ret.metadata["deser_times"] if ret else None,
+ "loops": ret.metadata["loops"] if ret else None,
+ "latency": event_data["ret_t"][1] - event_data["sent_t"][1] if ret else None
+ }
+ data.append(row)
+
+ # Create a DataFrame and save it as a pickle file
+ df = pd.DataFrame(data)
+ df.to_pickle(filename)
+
+def main():
+ runtime = FlinkRuntime("ds-movie-in", "ds-movie-out", 8081)
+ runtime.init(bundle_time=5, bundle_size=10)
+
+ print(frontend_op.dataflow.to_dot())
+ # dead_node_elimination([], [frontend_op])
+ print(frontend_op.dataflow.to_dot())
+ input()
+
+
+ runtime.add_operator(compose_review_op)
+ runtime.add_operator(user_op)
+ runtime.add_operator(movie_info_op)
+ runtime.add_operator(movie_id_op)
+ runtime.add_operator(plot_op)
+ runtime.add_stateless_operator(frontend_op)
+ runtime.add_stateless_operator(unique_id_op)
+ runtime.add_stateless_operator(text_op)
+
+ runtime.run(run_async=True)
+ populate_user(runtime)
+ populate_movie(runtime)
+ runtime.producer.flush()
+ time.sleep(1)
+
+ input()
+
+ # with Pool(threads) as p:
+ # results = p.map(benchmark_runner, range(threads))
+
+ # results = {k: v for d in results for k, v in d.items()}
+ results = benchmark_runner(0)
+
+ print("last result:")
+ print(list(results.values())[-1])
+ t = len(results)
+ r = 0
+ for result in results.values():
+ if result["ret"] is not None:
+ print(result)
+ r += 1
+ print(f"{r}/{t} results recieved.")
+ write_dict_to_pkl(results, "test2.pkl")
+
+if __name__ == "__main__":
+ main()
+
diff --git a/deathstar_movie_review/entities/__init__.py b/deathstar_movie_review/entities/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/deathstar_movie_review/entities/compose_review.py b/deathstar_movie_review/entities/compose_review.py
new file mode 100644
index 0000000..7423c4b
--- /dev/null
+++ b/deathstar_movie_review/entities/compose_review.py
@@ -0,0 +1,59 @@
+from typing import Any
+
+from src.cascade.dataflow.operator import StatefulOperator
+
+
+class ComposeReview:
+ def __init__(self, req_id: str, *args): # *args is a temporary hack to allow for creation of composereview on the fly
+ self.req_id = req_id
+ self.review_data = {}
+
+ def upload_unique_id(self, review_id: int):
+ self.review_data["review_id"] = review_id
+
+ # could use the User class instead?
+ def upload_user_id(self, user_id: str):
+ self.review_data["userId"] = user_id
+
+ def upload_movie_id(self, movie_id: str):
+ self.review_data["movieId"] = movie_id
+
+ def upload_rating(self, rating: int):
+ self.review_data["rating"] = rating
+
+ def upload_text(self, text: str):
+ self.review_data["text"] = text
+
+ def get_data(self):
+ return self.review_data
+
+def upload_unique_id_compiled(variable_map: dict[str, Any], state: ComposeReview) -> Any:
+ state.review_data["review_id"] = variable_map["review_id"]
+
+def upload_user_id_compiled(variable_map: dict[str, Any], state: ComposeReview) -> Any:
+ state.review_data["userId"] = variable_map["user_id"]
+
+def upload_movie_id_compiled(variable_map: dict[str, Any], state: ComposeReview) -> Any:
+ state.review_data["movieId"] = variable_map["movie_id"]
+
+def upload_rating_compiled(variable_map: dict[str, Any], state: ComposeReview) -> Any:
+ state.review_data["rating"] = variable_map["rating"]
+
+def upload_text_compiled(variable_map: dict[str, Any], state: ComposeReview) -> Any:
+ state.review_data["text"] = variable_map["text"]
+
+def get_data_compiled(variable_map: dict[str, Any], state: ComposeReview) -> Any:
+ return state.review_data
+
+compose_review_op = StatefulOperator(
+ ComposeReview,
+ {
+ "upload_unique_id": upload_unique_id_compiled,
+ "upload_user_id": upload_user_id_compiled,
+ "upload_movie_id": upload_movie_id_compiled,
+ "upload_rating": upload_rating_compiled,
+ "upload_text": upload_text_compiled,
+ "get_data": get_data_compiled,
+ },
+ {}
+)
\ No newline at end of file
diff --git a/deathstar_movie_review/entities/frontend.py b/deathstar_movie_review/entities/frontend.py
new file mode 100644
index 0000000..db75bc2
--- /dev/null
+++ b/deathstar_movie_review/entities/frontend.py
@@ -0,0 +1,188 @@
+from typing import Any
+import uuid
+
+from cascade.dataflow.dataflow import CollectNode, CollectTarget, DataFlow, Edge, InvokeMethod, OpNode, StatelessOpNode
+from cascade.dataflow.operator import StatelessOperator
+from deathstar_movie_review.entities.compose_review import ComposeReview
+from deathstar_movie_review.entities.movie import MovieId
+from deathstar_movie_review.entities.user import User
+
+
+# unique_id is stateless
+class UniqueId():
+ @staticmethod
+ def upload_unique_id_2(review: ComposeReview):
+ review_id = uuid.uuid1().int >> 64
+ review.upload_unique_id(review_id)
+
+# text is stateless
+class Text():
+ @staticmethod
+ def upload_text_2(review: ComposeReview, text: str):
+ review.upload_text(text)
+
+CHAR_LIMIT = 50
+
+# frontend is made stateless
+class Frontend():
+ @staticmethod
+ def compose(review: ComposeReview, user: User, title: MovieId, rating: int, text: str):
+
+ # dead node elimination will remove "returning back" to the original function
+ #
+ # cascade could theoritically allow for more advanced analysis,
+ # that would enable all these to run in parallel. However, this is only
+ # possible because
+ # 1. the individual functions don't depend on each other
+ # 2. the ordering of side-effects does not matter
+ UniqueId.upload_unique_id_2(review)
+ user.upload_user(review)
+ title.upload_movie(review, rating)
+
+ text = text[:CHAR_LIMIT] # an operation like this could be reorderd for better efficiency!
+ Text.upload_text_2(review, text)
+
+###### COMPILED FUNCTIONS ######
+
+### UPLOAD UNIQUE ###
+
+def upload_unique_compiled_0(variable_map: dict[str, Any]):
+ variable_map["review_id"] = uuid.uuid1().int >> 64
+
+unique_id_op = StatelessOperator(
+ {
+ "upload_unique": upload_unique_compiled_0,
+ },
+ None
+)
+
+df = DataFlow("upload_unique_id")
+n0 = StatelessOpNode(unique_id_op, InvokeMethod("upload_unique"))
+n1 = OpNode(ComposeReview, InvokeMethod("upload_unique_id"), read_key_from="review")
+df.entry = n0
+unique_id_op.dataflow = df
+
+### TEXT ###
+
+text_op = StatelessOperator(
+ {},
+ None
+)
+
+df = DataFlow("upload_text")
+n0 = OpNode(ComposeReview, InvokeMethod("upload_text"), read_key_from="review")
+df.entry = n0
+text_op.dataflow = df
+
+### FRONTEND ###
+
+def compose_compiled_0(variable_map: dict[str, Any]):
+ pass
+
+
+frontend_op = StatelessOperator(
+ {
+ "empty": compose_compiled_0,
+ },
+ None
+)
+
+def frontend_df_serial():
+ # This dataflow calls many other dataflows.
+ # It could be more useful to have a "Dataflow" node
+ df = DataFlow("compose")
+ n0 = StatelessOpNode(frontend_op, InvokeMethod("empty"))
+
+ # Upload Unique DF
+ n1_a = StatelessOpNode(unique_id_op, InvokeMethod("upload_unique"))
+ n1_b = OpNode(ComposeReview, InvokeMethod("upload_unique_id"), read_key_from="review")
+
+ n2 = StatelessOpNode(frontend_op, InvokeMethod("empty"))
+
+ # Upload User DF
+ n3_a = OpNode(User, InvokeMethod("upload_user_compiled_0"), read_key_from="user")
+ n3_b = OpNode(ComposeReview, InvokeMethod("upload_user_id"), read_key_from="review")
+
+ n4 = StatelessOpNode(frontend_op, InvokeMethod("empty"))
+
+ # Upload Movie DF
+ n5_a = OpNode(MovieId, InvokeMethod("upload_movie_cond"), read_key_from="title", is_conditional=True)
+ n5_b = OpNode(ComposeReview, InvokeMethod("upload_movie_id"), read_key_from="review")
+ n5_c = OpNode(ComposeReview, InvokeMethod("upload_rating"), read_key_from="review")
+
+ n6 = StatelessOpNode(frontend_op, InvokeMethod("empty"))
+
+ # Upload Text DF
+ n7 = OpNode(ComposeReview, InvokeMethod("upload_text"), read_key_from="review")
+
+ n8 = StatelessOpNode(frontend_op, InvokeMethod("empty"))
+
+ df.add_edge(Edge(n0, n1_a))
+ df.add_edge(Edge(n1_a, n1_b))
+ df.add_edge(Edge(n1_b, n2))
+
+ df.add_edge(Edge(n2, n3_a))
+ df.add_edge(Edge(n3_a, n3_b))
+ df.add_edge(Edge(n3_b, n4))
+
+ df.add_edge(Edge(n4, n5_a))
+ df.add_edge(Edge(n5_a, n5_b, if_conditional=True))
+ df.add_edge(Edge(n5_a, n5_c, if_conditional=False))
+ df.add_edge(Edge(n5_b, n6))
+ df.add_edge(Edge(n5_c, n6))
+
+ df.add_edge(Edge(n6, n7))
+ df.add_edge(Edge(n7, n8))
+
+ df.entry = n0
+ return df
+
+def frontend_df_parallel():
+ # This dataflow calls many other dataflows.
+ # It could be more useful to have a "Dataflow" node
+ df = DataFlow("compose")
+ # n0 = StatelessOpNode(frontend_op, InvokeMethod("empty"))
+ ct = CollectNode(assign_result_to="results", read_results_from="dummy")
+
+ # Upload Unique DF
+ n1_a = StatelessOpNode(unique_id_op, InvokeMethod("upload_unique"))
+ n1_b = OpNode(ComposeReview, InvokeMethod("upload_unique_id"), read_key_from="review", collect_target=CollectTarget(ct, 4, 0))
+
+
+ # Upload User DF
+ n3_a = OpNode(User, InvokeMethod("upload_user_compiled_0"), read_key_from="user")
+ n3_b = OpNode(ComposeReview, InvokeMethod("upload_user_id"), read_key_from="review", collect_target=CollectTarget(ct, 4, 1))
+
+
+ # Upload Movie DF
+ n5_a = OpNode(MovieId, InvokeMethod("upload_movie_cond"), read_key_from="title", is_conditional=True)
+ n5_b = OpNode(ComposeReview, InvokeMethod("upload_movie_id"), read_key_from="review", collect_target=CollectTarget(ct, 4, 2))
+ n5_c = OpNode(ComposeReview, InvokeMethod("upload_rating"), read_key_from="review", collect_target=CollectTarget(ct, 4, 2))
+
+
+ # Upload Text DF
+ n7 = OpNode(ComposeReview, InvokeMethod("upload_text"), read_key_from="review",collect_target=CollectTarget(ct, 4, 3))
+
+
+ # df.add_edge(Edge(n0, n1_a))
+ df.add_edge(Edge(n1_a, n1_b))
+ df.add_edge(Edge(n1_b, ct))
+
+ # df.add_edge(Edge(n0, n3_a))
+ df.add_edge(Edge(n3_a, n3_b))
+ df.add_edge(Edge(n3_b, ct))
+
+ # df.add_edge(Edge(n0, n5_a))
+ df.add_edge(Edge(n5_a, n5_b, if_conditional=True))
+ df.add_edge(Edge(n5_a, n5_c, if_conditional=False))
+ df.add_edge(Edge(n5_b, ct))
+ df.add_edge(Edge(n5_c, ct))
+
+ # df.add_edge(Edge(n0, n7))
+ df.add_edge(Edge(n7, ct))
+
+ df.entry = [n1_a, n3_a, n5_a, n7]
+ return df
+
+frontend_op.dataflow = frontend_df_parallel()
+
diff --git a/deathstar_movie_review/entities/movie.py b/deathstar_movie_review/entities/movie.py
new file mode 100644
index 0000000..ade4d19
--- /dev/null
+++ b/deathstar_movie_review/entities/movie.py
@@ -0,0 +1,72 @@
+from typing import Any
+from cascade.dataflow.dataflow import DataFlow, Edge, InvokeMethod, OpNode
+from cascade.dataflow.operator import StatefulOperator
+from deathstar_movie_review.entities.compose_review import ComposeReview
+from deathstar_movie_review.entities.user import User
+
+
+class MovieId:
+ # key: 'title'
+ def __init__(self, title: str, movie_id: str):
+ self.title = title
+ self.movie_id = movie_id
+
+ def upload_movie(self, review: ComposeReview, rating: int):
+ if self.movie_id is not None:
+ review.upload_movie_id(self.movie_id)
+ else:
+ review.upload_rating(rating)
+
+
+def upload_movie_compiled_cond_0(variable_map: dict[str, Any], state: MovieId) -> Any:
+ variable_map["movie_id"] = state.movie_id # SSA
+ return variable_map["movie_id"] is not None
+
+movie_id_op = StatefulOperator(
+ MovieId,
+ {
+ "upload_movie_cond": upload_movie_compiled_cond_0
+ },
+ {}
+)
+
+def upload_movie_df():
+ df = DataFlow("movieId_upload_movie")
+ n0 = OpNode(MovieId, InvokeMethod("upload_movie_cond"), read_key_from="title", is_conditional=True)
+ n1 = OpNode(ComposeReview, InvokeMethod("upload_movie_id"), read_key_from="review")
+ n2 = OpNode(ComposeReview, InvokeMethod("upload_rating"), read_key_from="review")
+
+ df.add_edge(Edge(n0, n1, if_conditional=True))
+ df.add_edge(Edge(n0, n2, if_conditional=False))
+ df.entry = n0
+ return df
+
+movie_id_op.dataflows["upload_movie"] = upload_movie_df()
+
+
+
+### Other movie-related operators
+
+# key: movie_id
+
+class Plot:
+ def __init__(self, movie_id: str, plot: str):
+ self.movie_id = movie_id
+ self.plot = plot
+
+class MovieInfo:
+ def __init__(self, movie_id: str, info: dict):
+ self.movie_id = movie_id
+ self.info = info
+
+movie_info_op = StatefulOperator(
+ MovieInfo,
+ {},
+ {}
+)
+
+plot_op = StatefulOperator(
+ Plot,
+ {},
+ {}
+)
\ No newline at end of file
diff --git a/deathstar_movie_review/entities/user.py b/deathstar_movie_review/entities/user.py
new file mode 100644
index 0000000..2b244a9
--- /dev/null
+++ b/deathstar_movie_review/entities/user.py
@@ -0,0 +1,36 @@
+from typing import Any
+from deathstar_movie_review.entities.compose_review import ComposeReview
+from src.cascade.dataflow.dataflow import DataFlow, Edge, InvokeMethod, OpNode
+from src.cascade.dataflow.operator import StatefulOperator
+
+
+class User:
+ def __init__(self, username: str, user_data: dict):
+ self.username = username
+ self.user_data = user_data
+
+ def upload_user(self, review: ComposeReview):
+ review.upload_user_id(self.user_data["userId"])
+
+
+def upload_user_compiled_0(variable_map: dict[str, Any], state: User) -> Any:
+ variable_map["user_id"] = state.user_data["userId"]
+
+user_op = StatefulOperator(
+ User,
+ {
+ "upload_user_compiled_0": upload_user_compiled_0,
+ },
+ {}
+)
+
+def upload_df():
+ df = DataFlow("user_upload_user")
+ n0 = OpNode(User, InvokeMethod("upload_user_compiled_0"), read_key_from="username")
+ n1 = OpNode(ComposeReview, InvokeMethod("upload_user_id"), read_key_from="review")
+
+ df.add_edge(Edge(n0, n1))
+ df.entry = n0
+ return df
+
+user_op.dataflows["upload_user"] = upload_df()
\ No newline at end of file
diff --git a/deathstar_movie_review/movie_data.py b/deathstar_movie_review/movie_data.py
new file mode 100644
index 0000000..a25f23f
--- /dev/null
+++ b/deathstar_movie_review/movie_data.py
@@ -0,0 +1,2571 @@
+movie_data = [{"MovieId": "299534", "Title": "Avengers: Endgame", "Casts": [], "PlotId": "299534",
+ "ThumbnailIds": ["/or06FN3Dka5tukK1e9sl16pB3iy.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.6,
+ "NumRating": 4789},
+ {"MovieId": "543103", "Title": "Kamen Rider Heisei Generations FOREVER", "Casts": [], "PlotId": "543103",
+ "ThumbnailIds": ["/6sOFQDlkY6El1B2P5gklzJfVdsT.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.8,
+ "NumRating": 12}, {"MovieId": "299537", "Title": "Captain Marvel", "Casts": [], "PlotId": "299537",
+ "ThumbnailIds": ["/AtsgWhDnHTq68L0lLsUrCnM7TjG.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.1, "NumRating": 4726},
+ {"MovieId": "447404", "Title": "Pok\u00e9mon Detective Pikachu", "Casts": [], "PlotId": "447404",
+ "ThumbnailIds": ["/wgQ7APnFpf1TuviKHXeEe3KnsTV.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.2,
+ "NumRating": 120}, {"MovieId": "456740", "Title": "Hellboy", "Casts": [], "PlotId": "456740",
+ "ThumbnailIds": ["/bk8LyaMqUtaQ9hUShuvFznQYQKR.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.2, "NumRating": 320},
+ {"MovieId": "537915", "Title": "After", "Casts": [], "PlotId": "537915",
+ "ThumbnailIds": ["/u3B2YKUjWABcxXZ6Nm9h10hLUbh.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 819},
+ {"MovieId": "299536", "Title": "Avengers: Infinity War", "Casts": [], "PlotId": "299536",
+ "ThumbnailIds": ["/7WsyChQLEftFiDOVTGkv3hFpyyt.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.3,
+ "NumRating": 13379},
+ {"MovieId": "495925", "Title": "Doraemon the Movie: Nobita's Treasure Island", "Casts": [],
+ "PlotId": "495925", "ThumbnailIds": ["/cmJ71gdZxCqkMUvGwWgSg3MK7pC.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.3, "NumRating": 16},
+ {"MovieId": "438650", "Title": "Cold Pursuit", "Casts": [], "PlotId": "438650",
+ "ThumbnailIds": ["/otK0H9H1w3JVGJjad5Kzx3Z9kt2.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.2,
+ "NumRating": 445}, {"MovieId": "287947", "Title": "Shazam!", "Casts": [], "PlotId": "287947",
+ "ThumbnailIds": ["/xnopI5Xtky18MPhK40cZAGAOVeV.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.2, "NumRating": 1442},
+ {"MovieId": "487297", "Title": "What Men Want", "Casts": [], "PlotId": "487297",
+ "ThumbnailIds": ["/30IiwvIRqPGjUV0bxJkZfnSiCL.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.8,
+ "NumRating": 222}, {"MovieId": "450465", "Title": "Glass", "Casts": [], "PlotId": "450465",
+ "ThumbnailIds": ["/svIDTNUoajS8dLEo7EosxvyAsgJ.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.5, "NumRating": 2695},
+ {"MovieId": "24428", "Title": "The Avengers", "Casts": [], "PlotId": "24428",
+ "ThumbnailIds": ["/cezWGskPY5x7GaglTTRN4Fugfb8.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.6,
+ "NumRating": 19269}, {"MovieId": "284054", "Title": "Black Panther", "Casts": [], "PlotId": "284054",
+ "ThumbnailIds": ["/uxzzxijgPIY7slzFvMotPv8wjKA.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.4, "NumRating": 11967},
+ {"MovieId": "166428", "Title": "How to Train Your Dragon: The Hidden World", "Casts": [],
+ "PlotId": "166428", "ThumbnailIds": ["/xvx4Yhf0DVH8G4LzNISpMfFBDy2.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.6, "NumRating": 1718},
+ {"MovieId": "468224", "Title": "Tolkien", "Casts": [], "PlotId": "468224",
+ "ThumbnailIds": ["/5ElXRKi773koW0mAnRjpNTpgZXZ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.9,
+ "NumRating": 8},
+ {"MovieId": "445629", "Title": "Fighting with My Family", "Casts": [], "PlotId": "445629",
+ "ThumbnailIds": ["/3TZCBAdKQiz0cGKGEjZiyZUA01O.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.5,
+ "NumRating": 174},
+ {"MovieId": "283995", "Title": "Guardians of the Galaxy Vol. 2", "Casts": [], "PlotId": "283995",
+ "ThumbnailIds": ["/y4MBh0EjBlMuOzv9axM4qJlmhzz.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.7,
+ "NumRating": 12212},
+ {"MovieId": "99861", "Title": "Avengers: Age of Ultron", "Casts": [], "PlotId": "99861",
+ "ThumbnailIds": ["/t90Y3G8UGQp0f0DrP60wRu9gfrH.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 13126}, {"MovieId": "532671", "Title": "The Prodigy", "Casts": [], "PlotId": "532671",
+ "ThumbnailIds": ["/yyejodyk3lWncVjVhhrEkPctY9o.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.9, "NumRating": 161},
+ {"MovieId": "920", "Title": "Cars", "Casts": [], "PlotId": "920",
+ "ThumbnailIds": ["/5damnMcRFKSjhCirgX3CMa88MBj.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 7366}, {"MovieId": "284053", "Title": "Thor: Ragnarok", "Casts": [], "PlotId": "284053",
+ "ThumbnailIds": ["/rzRwTcFvttcN1ZpX2xv4j3tSdJu.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.5, "NumRating": 11089},
+ {"MovieId": "535167", "Title": "The Wandering Earth", "Casts": [], "PlotId": "535167",
+ "ThumbnailIds": ["/yzqpJcJT79CLTNdZVU7HHee6L3a.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.5,
+ "NumRating": 101},
+ {"MovieId": "457799", "Title": "Extremely Wicked, Shockingly Evil and Vile", "Casts": [],
+ "PlotId": "457799", "ThumbnailIds": ["/zSuJ3r5zr5T26tTxyygHhgkUAIM.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.1, "NumRating": 272},
+ {"MovieId": "157433", "Title": "Pet Sematary", "Casts": [], "PlotId": "157433",
+ "ThumbnailIds": ["/7SPhr7Qj39vbnfF9O2qHRYaKHAL.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 383}, {"MovieId": "263115", "Title": "Logan", "Casts": [], "PlotId": "263115",
+ "ThumbnailIds": ["/gGBu0hKw9BGddG8RkRAMX7B6NDB.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.8, "NumRating": 12092},
+ {"MovieId": "118340", "Title": "Guardians of the Galaxy", "Casts": [], "PlotId": "118340",
+ "ThumbnailIds": ["/y31QB9kn3XSudA15tV7UWQ9XLuW.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.9,
+ "NumRating": 17578},
+ {"MovieId": "576393", "Title": "Fall in Love at First Kiss", "Casts": [], "PlotId": "576393",
+ "ThumbnailIds": ["/wtaSH8MfJSCEIrrEX9SQuHdU5sl.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 20},
+ {"MovieId": "324857", "Title": "Spider-Man: Into the Spider-Verse", "Casts": [], "PlotId": "324857",
+ "ThumbnailIds": ["/iiZZdoQBEYBv6id8su7ImL0oCbD.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.4,
+ "NumRating": 3649}, {"MovieId": "424783", "Title": "Bumblebee", "Casts": [], "PlotId": "424783",
+ "ThumbnailIds": ["/fw02ONlDhrYjTSZV8XO6hhU3ds3.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.4, "NumRating": 2221},
+ {"MovieId": "10195", "Title": "Thor", "Casts": [], "PlotId": "10195",
+ "ThumbnailIds": ["/9zDwvsISU8bR15R2yN3kh1lfqve.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 12476}, {"MovieId": "297802", "Title": "Aquaman", "Casts": [], "PlotId": "297802",
+ "ThumbnailIds": ["/5Kg76ldv7VxeX9YlcQXiowHgdX6.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.8, "NumRating": 5783},
+ {"MovieId": "363088", "Title": "Ant-Man and the Wasp", "Casts": [], "PlotId": "363088",
+ "ThumbnailIds": ["/eivQmS3wqzqnQWILHLc4FsEfcXP.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 5977},
+ {"MovieId": "280217", "Title": "The Lego Movie 2: The Second Part", "Casts": [], "PlotId": "280217",
+ "ThumbnailIds": ["/QTESAsBVZwjtGJNDP7utiGV37z.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 459}, {"MovieId": "137113", "Title": "Edge of Tomorrow", "Casts": [], "PlotId": "137113",
+ "ThumbnailIds": ["/tpoVEYvm6qcXueZrQYJNRLXL88s.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.6, "NumRating": 7924},
+ {"MovieId": "449562", "Title": "The Hustle", "Casts": [], "PlotId": "449562",
+ "ThumbnailIds": ["/qibqW5Dnvqp4hcEnoTARbQgxwJy.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 11}, {"MovieId": "329996", "Title": "Dumbo", "Casts": [], "PlotId": "329996",
+ "ThumbnailIds": ["/279PwJAcelI4VuBtdzrZASqDPQr.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 1102},
+ {"MovieId": "485811", "Title": "Redcon-1", "Casts": [], "PlotId": "485811",
+ "ThumbnailIds": ["/jOYUbe61DQiY628inVkR1KERS30.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.2,
+ "NumRating": 31}, {"MovieId": "491418", "Title": "Instant Family", "Casts": [], "PlotId": "491418",
+ "ThumbnailIds": ["/dic3GdmMpxxfkCQfvZnasb5ZkSG.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.5, "NumRating": 690},
+ {"MovieId": "315635", "Title": "Spider-Man: Homecoming", "Casts": [], "PlotId": "315635",
+ "ThumbnailIds": ["/kY2c7wKgOfQjvbqe7yVzLTYkxJO.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 11238},
+ {"MovieId": "122917", "Title": "The Hobbit: The Battle of the Five Armies", "Casts": [],
+ "PlotId": "122917", "ThumbnailIds": ["/9zRzFJuaj0CHIOhAkcCcFTvyu2X.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.3, "NumRating": 8241},
+ {"MovieId": "526050", "Title": "Little", "Casts": [], "PlotId": "526050",
+ "ThumbnailIds": ["/4MDB6jJl3U7xK1Gw64zIqt9pQA4.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 38},
+ {"MovieId": "480414", "Title": "The Curse of La Llorona", "Casts": [], "PlotId": "480414",
+ "ThumbnailIds": ["/jhZlXSnFUpNiLAek9EkPrtLEWQI.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.5,
+ "NumRating": 243}, {"MovieId": "522681", "Title": "Escape Room", "Casts": [], "PlotId": "522681",
+ "ThumbnailIds": ["/8Ls1tZ6qjGzfGHjBB7ihOnf7f0b.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.3, "NumRating": 1069},
+ {"MovieId": "500852", "Title": "Miss Bala", "Casts": [], "PlotId": "500852",
+ "ThumbnailIds": ["/ae9yrSAS7nLZPbbkOm61pSuIqeo.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 52}, {"MovieId": "376865", "Title": "High Life", "Casts": [], "PlotId": "376865",
+ "ThumbnailIds": ["/wElOvH7H6sLElsTOLu1MY6oWRUx.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.8, "NumRating": 105},
+ {"MovieId": "500682", "Title": "The Highwaymen", "Casts": [], "PlotId": "500682",
+ "ThumbnailIds": ["/4bRYg4l12yDuJvAfqvUOPnBrxno.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 514},
+ {"MovieId": "337339", "Title": "The Fate of the Furious", "Casts": [], "PlotId": "337339",
+ "ThumbnailIds": ["/dImWM7GJqryWJO9LHa3XQ8DD5NH.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.9,
+ "NumRating": 6236}, {"MovieId": "1726", "Title": "Iron Man", "Casts": [], "PlotId": "1726",
+ "ThumbnailIds": ["/848chlIWVT41VtAAgyh9bWymAYb.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.5, "NumRating": 15386},
+ {"MovieId": "1771", "Title": "Captain America: The First Avenger", "Casts": [], "PlotId": "1771",
+ "ThumbnailIds": ["/vSNxAJTlD0r02V9sPYpOjqDZXUK.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.9,
+ "NumRating": 12515},
+ {"MovieId": "271110", "Title": "Captain America: Civil War", "Casts": [], "PlotId": "271110",
+ "ThumbnailIds": ["/kSBXou5Ac7vEqKd97wotJumyJvU.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 13696}, {"MovieId": "458723", "Title": "Us", "Casts": [], "PlotId": "458723",
+ "ThumbnailIds": ["/ux2dU1jQ2ACIMShzB3yP93Udpzc.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.2, "NumRating": 1268},
+ {"MovieId": "566555", "Title": "Detective Conan: The Fist of Blue Sapphire", "Casts": [],
+ "PlotId": "566555", "ThumbnailIds": ["/1GyvpwvgswOrHvxjnw2FBLNkTyo.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.0, "NumRating": 2},
+ {"MovieId": "390634", "Title": "Fate/stay night: Heaven\u2019s Feel II. lost butterfly", "Casts": [],
+ "PlotId": "390634", "ThumbnailIds": ["/4tS0iyKQBDFqVpVcH21MSJwXZdq.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 4.7, "NumRating": 46},
+ {"MovieId": "375588", "Title": "Robin Hood", "Casts": [], "PlotId": "375588",
+ "ThumbnailIds": ["/AiRfixFcfTkNbn2A73qVJPlpkUo.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.7,
+ "NumRating": 1095},
+ {"MovieId": "338952", "Title": "Fantastic Beasts: The Crimes of Grindelwald", "Casts": [],
+ "PlotId": "338952", "ThumbnailIds": ["/fMMrl8fD9gRCFJvsx0SuFwkEOop.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.9, "NumRating": 4636},
+ {"MovieId": "76338", "Title": "Thor: The Dark World", "Casts": [], "PlotId": "76338",
+ "ThumbnailIds": ["/bnX5PqAdQZRXSw3aX3DutDcdso5.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 9932},
+ {"MovieId": "399579", "Title": "Alita: Battle Angel", "Casts": [], "PlotId": "399579",
+ "ThumbnailIds": ["/xRWht48C2V8XNfzvPehyClOvDni.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 1951}, {"MovieId": "512196", "Title": "Happy Death Day 2U", "Casts": [], "PlotId": "512196",
+ "ThumbnailIds": ["/4tdnePOkOOzwuGPEOAHp8UA4vqx.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.1, "NumRating": 714},
+ {"MovieId": "245891", "Title": "John Wick", "Casts": [], "PlotId": "245891",
+ "ThumbnailIds": ["/b9uYMMbm87IBFOq59pppvkkkgNg.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.2,
+ "NumRating": 9589}, {"MovieId": "347375", "Title": "Mile 22", "Casts": [], "PlotId": "347375",
+ "ThumbnailIds": ["/2L8ehd95eSW9x7KINYtZmRkAlrZ.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.0, "NumRating": 882},
+ {"MovieId": "10138", "Title": "Iron Man 2", "Casts": [], "PlotId": "10138",
+ "ThumbnailIds": ["/ArqpkNYGfcTIA6umWt6xihfIZZv.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.8,
+ "NumRating": 12070}, {"MovieId": "287424", "Title": "Maggie", "Casts": [], "PlotId": "287424",
+ "ThumbnailIds": ["/mFt7Oo3pf8f1BZAdWLyUpYM63aT.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.3, "NumRating": 947},
+ {"MovieId": "335983", "Title": "Venom", "Casts": [], "PlotId": "335983",
+ "ThumbnailIds": ["/2uNW4WbgBXL25BAbXGLnLqX71Sw.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 5909},
+ {"MovieId": "671", "Title": "Harry Potter and the Philosopher's Stone", "Casts": [], "PlotId": "671",
+ "ThumbnailIds": ["/dCtFvscYcXQKTNvyyaQr2g2UacJ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.8,
+ "NumRating": 13675},
+ {"MovieId": "454294", "Title": "The Kid Who Would Be King", "Casts": [], "PlotId": "454294",
+ "ThumbnailIds": ["/kBuvLX6zynQP0sjyqbXV4jNaZ4E.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 160},
+ {"MovieId": "22", "Title": "Pirates of the Caribbean: The Curse of the Black Pearl", "Casts": [],
+ "PlotId": "22", "ThumbnailIds": ["/tkt9xR1kNX5R9rCebASKck44si2.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.7, "NumRating": 12157},
+ {"MovieId": "424694", "Title": "Bohemian Rhapsody", "Casts": [], "PlotId": "424694",
+ "ThumbnailIds": ["/lHu1wtNaczFPGFDTrjCSzeLPTKN.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.1,
+ "NumRating": 7236}, {"MovieId": "504172", "Title": "The Mule", "Casts": [], "PlotId": "504172",
+ "ThumbnailIds": ["/oeZh7yEz3PMnZLgBPhrafFHRbVz.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.5, "NumRating": 1390},
+ {"MovieId": "404368", "Title": "Ralph Breaks the Internet", "Casts": [], "PlotId": "404368",
+ "ThumbnailIds": ["/lvfIaThG5HA8THf76nghKinjjji.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.2,
+ "NumRating": 2406},
+ {"MovieId": "120", "Title": "The Lord of the Rings: The Fellowship of the Ring", "Casts": [],
+ "PlotId": "120", "ThumbnailIds": ["/56zTpe2xvaA4alU51sRWPoKPYZy.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.3, "NumRating": 14253},
+ {"MovieId": "395990", "Title": "Death Wish", "Casts": [], "PlotId": "395990",
+ "ThumbnailIds": ["/7FG13lLQcV9DC2Bhn0hjxc6AFXV.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.8,
+ "NumRating": 1158},
+ {"MovieId": "458156", "Title": "John Wick: Chapter 3 \u2013 Parabellum", "Casts": [], "PlotId": "458156",
+ "ThumbnailIds": ["/ziEuG1essDuWuC5lpWUaw1uXY2O.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0,
+ "NumRating": 0}, {"MovieId": "440161", "Title": "The Sisters Brothers", "Casts": [], "PlotId": "440161",
+ "ThumbnailIds": ["/7Tl2nZ6uvmxwK14Skbf9VFHEHpX.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.0, "NumRating": 517},
+ {"MovieId": "301351", "Title": "We Are Your Friends", "Casts": [], "PlotId": "301351",
+ "ThumbnailIds": ["/accc6f6h3Xi8kURvYpPoATOsm2Z.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.3,
+ "NumRating": 1218},
+ {"MovieId": "579598", "Title": "Vaarikkuzhiyile Kolapathakam", "Casts": [], "PlotId": "579598",
+ "ThumbnailIds": ["/qwDA7qSSQLwQ7JgDmHrflHFyQZf.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0,
+ "NumRating": 0}, {"MovieId": "442249", "Title": "The First Purge", "Casts": [], "PlotId": "442249",
+ "ThumbnailIds": ["/litjsBoiydO6JlO70uOX4N3WnNL.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.8, "NumRating": 1778},
+ {"MovieId": "218043", "Title": "Left Behind", "Casts": [], "PlotId": "218043",
+ "ThumbnailIds": ["/lWf8po2lGleVuzRB4lpHavVT1Lv.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 3.9,
+ "NumRating": 694}, {"MovieId": "383498", "Title": "Deadpool 2", "Casts": [], "PlotId": "383498",
+ "ThumbnailIds": ["/to0spRl1CMDvyUbOnbb4fTk3VAd.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.5, "NumRating": 8451},
+ {"MovieId": "487558", "Title": "BlacKkKlansman", "Casts": [], "PlotId": "487558",
+ "ThumbnailIds": ["/3ntR66u2SHZ2UA3r3DjF2Dl6Kwx.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.6,
+ "NumRating": 2892}, {"MovieId": "298250", "Title": "Jigsaw", "Casts": [], "PlotId": "298250",
+ "ThumbnailIds": ["/2mUqHJG7ZiGwZYIylczFCsRPbXM.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.1, "NumRating": 1837},
+ {"MovieId": "454983", "Title": "The Kissing Booth", "Casts": [], "PlotId": "454983",
+ "ThumbnailIds": ["/7Dktk2ST6aL8h9Oe5rpk903VLhx.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 3269},
+ {"MovieId": "263109", "Title": "Shaun the Sheep Movie", "Casts": [], "PlotId": "263109",
+ "ThumbnailIds": ["/aOHsNN1p2nuiF9WaMaCNXy0T80J.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.9,
+ "NumRating": 697},
+ {"MovieId": "3512", "Title": "Under Siege 2: Dark Territory", "Casts": [], "PlotId": "3512",
+ "ThumbnailIds": ["/6Z1p71nkm45cYuIZWOx5JSCYc0o.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.6,
+ "NumRating": 358}, {"MovieId": "460019", "Title": "Truth or Dare", "Casts": [], "PlotId": "460019",
+ "ThumbnailIds": ["/kdkNaQYZ7dhM80LsnAGKpH8ca2g.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.0, "NumRating": 1908},
+ {"MovieId": "284052", "Title": "Doctor Strange", "Casts": [], "PlotId": "284052",
+ "ThumbnailIds": ["/4PiiNGXj1KENTmCBHeN6Mskj2Fq.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 12292},
+ {"MovieId": "122", "Title": "The Lord of the Rings: The Return of the King", "Casts": [], "PlotId": "122",
+ "ThumbnailIds": ["/rCzpDGLbOoPwLjy3OAm5NUPOTrC.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.4,
+ "NumRating": 12967}, {"MovieId": "1585", "Title": "It's a Wonderful Life", "Casts": [], "PlotId": "1585",
+ "ThumbnailIds": ["/rgj6QjdyCeDrO9KGt1kusGyhvb2.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 8.2, "NumRating": 1986},
+ {"MovieId": "157336", "Title": "Interstellar", "Casts": [], "PlotId": "157336",
+ "ThumbnailIds": ["/nBNZadXqJSdt05SHLqgT0HuC5Gm.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.2,
+ "NumRating": 18294},
+ {"MovieId": "446021", "Title": "Bad Times at the El Royale", "Casts": [], "PlotId": "446021",
+ "ThumbnailIds": ["/iNtFgXqXPRMkm1QO8CHn5sHfUgE.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.8,
+ "NumRating": 1261},
+ {"MovieId": "464504", "Title": "A Madea Family Funeral", "Casts": [], "PlotId": "464504",
+ "ThumbnailIds": ["/sFvOTUlZrIxCLdmz1fC16wK0lme.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.5,
+ "NumRating": 305}, {"MovieId": "459992", "Title": "Long Shot", "Casts": [], "PlotId": "459992",
+ "ThumbnailIds": ["/m2ttWZ8rMRwIMT7zA48Jo6mTkDS.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.9, "NumRating": 35},
+ {"MovieId": "361292", "Title": "Suspiria", "Casts": [], "PlotId": "361292",
+ "ThumbnailIds": ["/dzWTnkert9EoiPWldWJ15dnfAFl.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.1,
+ "NumRating": 668}, {"MovieId": "155", "Title": "The Dark Knight", "Casts": [], "PlotId": "155",
+ "ThumbnailIds": ["/1hRoyzDtpgMU7Dz4JF22RANzQO7.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.4, "NumRating": 18634},
+ {"MovieId": "627", "Title": "Trainspotting", "Casts": [], "PlotId": "627",
+ "ThumbnailIds": ["/p1O3eFsdb0GEIYu87xlwV7P4jM1.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.0,
+ "NumRating": 5228}, {"MovieId": "11", "Title": "Star Wars", "Casts": [], "PlotId": "11",
+ "ThumbnailIds": ["/btTdmkgIvOi0FFip1sPuZI2oQG6.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 8.2, "NumRating": 11282},
+ {"MovieId": "428078", "Title": "Mortal Engines", "Casts": [], "PlotId": "428078",
+ "ThumbnailIds": ["/iteUvQKCW0EqNQrIVzZGJntYq9s.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 1597}, {"MovieId": "339877", "Title": "Loving Vincent", "Casts": [], "PlotId": "339877",
+ "ThumbnailIds": ["/56sq57kDm7XgyXBYrgJLumo0Jac.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 8.2, "NumRating": 1178},
+ {"MovieId": "351286", "Title": "Jurassic World: Fallen Kingdom", "Casts": [], "PlotId": "351286",
+ "ThumbnailIds": ["/c9XxwwhPHdaImA2f1WEfEsbhaFB.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.5,
+ "NumRating": 5800}, {"MovieId": "463684", "Title": "Velvet Buzzsaw", "Casts": [], "PlotId": "463684",
+ "ThumbnailIds": ["/3rViQPcrWthMNecp5XnkKev6BzW.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.3, "NumRating": 806},
+ {"MovieId": "407451", "Title": "A Wrinkle in Time", "Casts": [], "PlotId": "407451",
+ "ThumbnailIds": ["/yAcb58vipewa1BfNit2RjE6boXA.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.0,
+ "NumRating": 1046},
+ {"MovieId": "11430", "Title": "The Lion King 1\u00bd", "Casts": [], "PlotId": "11430",
+ "ThumbnailIds": ["/vDfaIoXTaQLUD5HVAv2hLIFKAcq.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.5,
+ "NumRating": 1547},
+ {"MovieId": "429617", "Title": "Spider-Man: Far from Home", "Casts": [], "PlotId": "429617",
+ "ThumbnailIds": ["/q1ZcgXatgXo58tUO3vEsrJhYSbu.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0,
+ "NumRating": 0},
+ {"MovieId": "672", "Title": "Harry Potter and the Chamber of Secrets", "Casts": [], "PlotId": "672",
+ "ThumbnailIds": ["/sdEOH0992YZ0QSxgXNIGLq1ToUi.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.7,
+ "NumRating": 11563},
+ {"MovieId": "348350", "Title": "Solo: A Star Wars Story", "Casts": [], "PlotId": "348350",
+ "ThumbnailIds": ["/3IGbjc5ZC5yxim5W0sFING2kdcz.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 3689}, {"MovieId": "502292", "Title": "The Last Summer", "Casts": [], "PlotId": "502292",
+ "ThumbnailIds": ["/roxH78GESToUjfd6Tc973jV0Wu7.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.1, "NumRating": 259},
+ {"MovieId": "68721", "Title": "Iron Man 3", "Casts": [], "PlotId": "68721",
+ "ThumbnailIds": ["/7XiGqZE8meUv7L4720L0tIDd7gO.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.9,
+ "NumRating": 13891}, {"MovieId": "489925", "Title": "Eighth Grade", "Casts": [], "PlotId": "489925",
+ "ThumbnailIds": ["/xTa9cLhGHfQ7084UvoPQ2bBXKqd.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.4, "NumRating": 480},
+ {"MovieId": "460885", "Title": "Mandy", "Casts": [], "PlotId": "460885",
+ "ThumbnailIds": ["/m0yf7J7HsKeK6E81SMRcX8vx6mH.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 591}, {"MovieId": "562", "Title": "Die Hard", "Casts": [], "PlotId": "562",
+ "ThumbnailIds": ["/mc7MubOLcIw3MDvnuQFrO9psfCa.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.7, "NumRating": 6137},
+ {"MovieId": "12536", "Title": "Home Alone 4", "Casts": [], "PlotId": "12536",
+ "ThumbnailIds": ["/359bHILkiD6ZVCq6WoHSD0UuJQV.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.2,
+ "NumRating": 513}, {"MovieId": "429300", "Title": "Adrift", "Casts": [], "PlotId": "429300",
+ "ThumbnailIds": ["/5gLDeADaETvwQlQow5szlyuhLbj.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 1207},
+ {"MovieId": "346910", "Title": "The Predator", "Casts": [], "PlotId": "346910",
+ "ThumbnailIds": ["/wMq9kQXTeQCHUZOG4fAe5cAxyUA.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.2,
+ "NumRating": 1934}, {"MovieId": "375262", "Title": "The Favourite", "Casts": [], "PlotId": "375262",
+ "ThumbnailIds": ["/cwBq0onfmeilU5xgqNNjJAMPfpw.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.7, "NumRating": 1925},
+ {"MovieId": "438799", "Title": "Overlord", "Casts": [], "PlotId": "438799",
+ "ThumbnailIds": ["/l76Rgp32z2UxjULApxGXAPpYdAP.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 965}, {"MovieId": "244506", "Title": "Camp X-Ray", "Casts": [], "PlotId": "244506",
+ "ThumbnailIds": ["/oGcmIqOAbV8npgY57u7tqzwPgc.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.8, "NumRating": 608},
+ {"MovieId": "400650", "Title": "Mary Poppins Returns", "Casts": [], "PlotId": "400650",
+ "ThumbnailIds": ["/uTVGku4LibMGyKgQvjBtv3OYfAX.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 1341},
+ {"MovieId": "450001", "Title": "Master Z: Ip Man Legacy", "Casts": [], "PlotId": "450001",
+ "ThumbnailIds": ["/6VxEvOF7QDovsG6ro9OVyjH07LF.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.1,
+ "NumRating": 133},
+ {"MovieId": "532321", "Title": "Re: Zero kara Hajimeru Isekai Seikatsu - Memory Snow", "Casts": [],
+ "PlotId": "532321", "ThumbnailIds": ["/xqR4ABkFTFYe8NDJi3knwWX7zfn.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 4.9, "NumRating": 7},
+ {"MovieId": "11024", "Title": "Scooby-Doo 2: Monsters Unleashed", "Casts": [], "PlotId": "11024",
+ "ThumbnailIds": ["/vEp6qw25qY2n03O9EaeiWNv89vb.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.6,
+ "NumRating": 1223},
+ {"MovieId": "287948", "Title": "The Transporter Refueled", "Casts": [], "PlotId": "287948",
+ "ThumbnailIds": ["/zW7oC3tucYLzu77xNbPbYjXUN4o.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.4,
+ "NumRating": 824}, {"MovieId": "340676", "Title": "Personal Shopper", "Casts": [], "PlotId": "340676",
+ "ThumbnailIds": ["/jDOWYDmJvGkVkVHf7Ru66gy6ry8.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.8, "NumRating": 584},
+ {"MovieId": "226857", "Title": "Endless Love", "Casts": [], "PlotId": "226857",
+ "ThumbnailIds": ["/wWkTnQosziIXEePOyunr53el8fe.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.8,
+ "NumRating": 977}, {"MovieId": "102899", "Title": "Ant-Man", "Casts": [], "PlotId": "102899",
+ "ThumbnailIds": ["/D6e8RJf2qUstnfkTslTXNTUAlT.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.1, "NumRating": 11671},
+ {"MovieId": "483906", "Title": "Polar", "Casts": [], "PlotId": "483906",
+ "ThumbnailIds": ["/qOBEpKVLl8Q9CZScbOcRRVISezV.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.3,
+ "NumRating": 781}, {"MovieId": "527261", "Title": "The Silence", "Casts": [], "PlotId": "527261",
+ "ThumbnailIds": ["/lTVOquzxw2DPF3MKuYd1ynz9F6H.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.0, "NumRating": 526},
+ {"MovieId": "411728", "Title": "The Professor and the Madman", "Casts": [], "PlotId": "411728",
+ "ThumbnailIds": ["/gtGCDLhfjW96qVarwctnuTpGOtD.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 88}, {"MovieId": "429351", "Title": "12 Strong", "Casts": [], "PlotId": "429351",
+ "ThumbnailIds": ["/j18021qCeRi3yUBtqd2UFj1c0RQ.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.6, "NumRating": 1278},
+ {"MovieId": "396806", "Title": "Anon", "Casts": [], "PlotId": "396806",
+ "ThumbnailIds": ["/xhBTO9n3fxy3HJt7WlR9h9vvVmk.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.9,
+ "NumRating": 659}, {"MovieId": "531309", "Title": "Brightburn", "Casts": [], "PlotId": "531309",
+ "ThumbnailIds": ["/roslEbKdY0WSgYaB5KXvPKY0bXS.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 0.0, "NumRating": 1},
+ {"MovieId": "454227", "Title": "Outlaw King", "Casts": [], "PlotId": "454227",
+ "ThumbnailIds": ["/rT49ousKUWN3dV7UlhaC9onTNdl.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 592}, {"MovieId": "76617", "Title": "Texas Chainsaw 3D", "Casts": [], "PlotId": "76617",
+ "ThumbnailIds": ["/p0kJOoNvwnWgmvKvL4GqlV3OPUV.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.3, "NumRating": 765},
+ {"MovieId": "440472", "Title": "The Upside", "Casts": [], "PlotId": "440472",
+ "ThumbnailIds": ["/hPZ2caow1PCND6qnerfgn6RTXdm.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 200}, {"MovieId": "514439", "Title": "Breakthrough", "Casts": [], "PlotId": "514439",
+ "ThumbnailIds": ["/t58dx7JIgchr9If5uxn3NmHaHoS.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 34},
+ {"MovieId": "378236", "Title": "The Emoji Movie", "Casts": [], "PlotId": "378236",
+ "ThumbnailIds": ["/f5pF4OYzh4wb1dYL2ARQNdqUsEZ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.4,
+ "NumRating": 1411}, {"MovieId": "490132", "Title": "Green Book", "Casts": [], "PlotId": "490132",
+ "ThumbnailIds": ["/7BsvSuDQuoqhWmU2fL7W2GOcZHU.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 8.3, "NumRating": 3120},
+ {"MovieId": "431530", "Title": "A Bad Moms Christmas", "Casts": [], "PlotId": "431530",
+ "ThumbnailIds": ["/gPNHolu7AGnrB7r5kvJRRTfwMFR.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.3,
+ "NumRating": 1013}, {"MovieId": "293660", "Title": "Deadpool", "Casts": [], "PlotId": "293660",
+ "ThumbnailIds": ["/inVq3FRqcYIRl2la8iZikYYxFNR.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.6, "NumRating": 19875},
+ {"MovieId": "11451", "Title": "Herbie Fully Loaded", "Casts": [], "PlotId": "11451",
+ "ThumbnailIds": ["/7lTfTZ8CDfXw09eAv3OOvsbCVgs.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.3,
+ "NumRating": 1168},
+ {"MovieId": "209112", "Title": "Batman v Superman: Dawn of Justice", "Casts": [], "PlotId": "209112",
+ "ThumbnailIds": ["/cGOPbv9wA5gEejkUN892JrveARt.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.8,
+ "NumRating": 11827}, {"MovieId": "86467", "Title": "Bel Ami", "Casts": [], "PlotId": "86467",
+ "ThumbnailIds": ["/59bgVtpX6MPy2d5zberJiRNkDDx.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.2, "NumRating": 308},
+ {"MovieId": "270010", "Title": "A Hologram for the King", "Casts": [], "PlotId": "270010",
+ "ThumbnailIds": ["/dDHJBd2iv7KTDzI7ybNLs31vvkM.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.9,
+ "NumRating": 574},
+ {"MovieId": "131631", "Title": "The Hunger Games: Mockingjay - Part 1", "Casts": [], "PlotId": "131631",
+ "ThumbnailIds": ["/gj282Pniaa78ZJfbaixyLXnXEDI.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.8,
+ "NumRating": 9938}, {"MovieId": "151960", "Title": "Planes", "Casts": [], "PlotId": "151960",
+ "ThumbnailIds": ["/c8ALtmmmtofVYOxSJG0S5ZcP2pF.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.8, "NumRating": 860},
+ {"MovieId": "13186", "Title": "Wrong Turn 2: Dead End", "Casts": [], "PlotId": "13186",
+ "ThumbnailIds": ["/ftmiyfrTrqTi5Qod62nGRP9BGPn.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.6,
+ "NumRating": 528}, {"MovieId": "1949", "Title": "Zodiac", "Casts": [], "PlotId": "1949",
+ "ThumbnailIds": ["/bgLyOROfFQI3FqYL7jQbiaV8lkN.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.4, "NumRating": 4148},
+ {"MovieId": "4922", "Title": "The Curious Case of Benjamin Button", "Casts": [], "PlotId": "4922",
+ "ThumbnailIds": ["/gjMR102u5hPdIAWX7O2rim8ZWgA.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.5,
+ "NumRating": 6474}, {"MovieId": "297762", "Title": "Wonder Woman", "Casts": [], "PlotId": "297762",
+ "ThumbnailIds": ["/imekS7f1OuHyUP2LAiTEM0zBzUz.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.3, "NumRating": 12322},
+ {"MovieId": "15789", "Title": "A Goofy Movie", "Casts": [], "PlotId": "15789",
+ "ThumbnailIds": ["/bycmMhO3iIoEDzP768sUjq2RV4T.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.9,
+ "NumRating": 796}, {"MovieId": "1724", "Title": "The Incredible Hulk", "Casts": [], "PlotId": "1724",
+ "ThumbnailIds": ["/gCQ4e8klADtzoa6bL7XLPnjiUIg.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.2, "NumRating": 6127},
+ {"MovieId": "254128", "Title": "San Andreas", "Casts": [], "PlotId": "254128",
+ "ThumbnailIds": ["/qey0tdcOp9kCDdEZuJ87yE3crSe.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 5035}, {"MovieId": "480530", "Title": "Creed II", "Casts": [], "PlotId": "480530",
+ "ThumbnailIds": ["/v3QyboWRoA4O9RbcsqH8tJMe8EB.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.7, "NumRating": 2107},
+ {"MovieId": "127585", "Title": "X-Men: Days of Future Past", "Casts": [], "PlotId": "127585",
+ "ThumbnailIds": ["/pb1IURTkK5rImP9ZV83lxJO2us7.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.5,
+ "NumRating": 9884}, {"MovieId": "2320", "Title": "Executive Decision", "Casts": [], "PlotId": "2320",
+ "ThumbnailIds": ["/wmgkFuEr8bHRAgs1HS5U46Rzo0I.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.1, "NumRating": 387},
+ {"MovieId": "574", "Title": "The Man Who Knew Too Much", "Casts": [], "PlotId": "574",
+ "ThumbnailIds": ["/vhUOukoJTWPfVpZOiKwrjdEV4OX.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.5,
+ "NumRating": 541}, {"MovieId": "68817", "Title": "Footloose", "Casts": [], "PlotId": "68817",
+ "ThumbnailIds": ["/kDpo6G7rYRHQ1bFhyLiJEW9ESPO.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 910},
+ {"MovieId": "141052", "Title": "Justice League", "Casts": [], "PlotId": "141052",
+ "ThumbnailIds": ["/eifGNCSDuxJeS1loAXil5bIGgvC.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 7362}, {"MovieId": "403119", "Title": "47 Meters Down", "Casts": [], "PlotId": "403119",
+ "ThumbnailIds": ["/2IgdRUTdHyoI3nFORcnnYEKOGIH.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.6, "NumRating": 1484},
+ {"MovieId": "332562", "Title": "A Star Is Born", "Casts": [], "PlotId": "332562",
+ "ThumbnailIds": ["/wrFpXMNBRj2PBiN4Z5kix51XaIZ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.5,
+ "NumRating": 5321}, {"MovieId": "260513", "Title": "Incredibles 2", "Casts": [], "PlotId": "260513",
+ "ThumbnailIds": ["/9lFKBtaVIhP7E2Pk0IY1CwTKTMZ.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.6, "NumRating": 6089},
+ {"MovieId": "401246", "Title": "The Square", "Casts": [], "PlotId": "401246",
+ "ThumbnailIds": ["/qYb8hzGGX7uBAVMYW1YFtcFeZhp.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.9,
+ "NumRating": 594}, {"MovieId": "393519", "Title": "Raw", "Casts": [], "PlotId": "393519",
+ "ThumbnailIds": ["/6kXW9b1FZXvB3l0mLMDbKwGgL3P.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.0, "NumRating": 1421},
+ {"MovieId": "241257", "Title": "Regression", "Casts": [], "PlotId": "241257",
+ "ThumbnailIds": ["/d1dtQQfJJWU31cGSFbfhyeLtsTm.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.5,
+ "NumRating": 1047}, {"MovieId": "12289", "Title": "Red Cliff", "Casts": [], "PlotId": "12289",
+ "ThumbnailIds": ["/uMiA2c1wrySRTI3f2ij5i2aCCya.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.2, "NumRating": 354},
+ {"MovieId": "181808", "Title": "Star Wars: The Last Jedi", "Casts": [], "PlotId": "181808",
+ "ThumbnailIds": ["/kOVEVeg59E0wsnXmF9nrh6OmWII.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 8361}, {"MovieId": "399402", "Title": "Hunter Killer", "Casts": [], "PlotId": "399402",
+ "ThumbnailIds": ["/a0j18XNVhP4RcW3wXwsqT0kVoQm.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.2, "NumRating": 623},
+ {"MovieId": "12123", "Title": "Chain Reaction", "Casts": [], "PlotId": "12123",
+ "ThumbnailIds": ["/62jE7wHYXLMBdYrdpTHpHsAu7NL.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.5,
+ "NumRating": 444},
+ {"MovieId": "1116", "Title": "The Wind That Shakes the Barley", "Casts": [], "PlotId": "1116",
+ "ThumbnailIds": ["/uLt7SpgPjfwkWqJpUk08xONISFZ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 304}, {"MovieId": "308504", "Title": "Last Knights", "Casts": [], "PlotId": "308504",
+ "ThumbnailIds": ["/jKcbKy4C9bbwcBWGkMQR70vBNXJ.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.3, "NumRating": 523},
+ {"MovieId": "19995", "Title": "Avatar", "Casts": [], "PlotId": "19995",
+ "ThumbnailIds": ["/kmcqlZGaSh20zpTbuoF0Cdn07dT.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 18408}, {"MovieId": "603", "Title": "The Matrix", "Casts": [], "PlotId": "603",
+ "ThumbnailIds": ["/hEpWvX6Bp79eLxY1kX5ZZJcme5U.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 8.1, "NumRating": 14155},
+ {"MovieId": "207768", "Title": "I Spit on Your Grave 2", "Casts": [], "PlotId": "207768",
+ "ThumbnailIds": ["/pHratkL4ETydC0Kcwgc3tOyvLK0.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 487},
+ {"MovieId": "145247", "Title": "The 100 Year-Old Man Who Climbed Out the Window and Disappeared",
+ "Casts": [], "PlotId": "145247", "ThumbnailIds": ["/cE51BdbaxpOWnfSKlSPNy5rzzFV.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.8, "NumRating": 491},
+ {"MovieId": "844", "Title": "2046", "Casts": [], "PlotId": "844",
+ "ThumbnailIds": ["/orslhtqVBbI1n7RECP7Fkhuodph.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.2,
+ "NumRating": 339}, {"MovieId": "77931", "Title": "The Smurfs 2", "Casts": [], "PlotId": "77931",
+ "ThumbnailIds": ["/mF2FXzZ3EktWoagU4fzoabNsK6J.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.7, "NumRating": 1186},
+ {"MovieId": "320007", "Title": "Victoria", "Casts": [], "PlotId": "320007",
+ "ThumbnailIds": ["/t66eAg7xBLGoSSoA8JtJtX3NKs1.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.8,
+ "NumRating": 609}, {"MovieId": "266396", "Title": "The Gunman", "Casts": [], "PlotId": "266396",
+ "ThumbnailIds": ["/lnUozDnDANTsDYEdsNsHC6b8IiS.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.7, "NumRating": 575},
+ {"MovieId": "272", "Title": "Batman Begins", "Casts": [], "PlotId": "272",
+ "ThumbnailIds": ["/dr6x4GyyegBWtinPBzipY02J2lV.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.6,
+ "NumRating": 11969}, {"MovieId": "374473", "Title": "I, Daniel Blake", "Casts": [], "PlotId": "374473",
+ "ThumbnailIds": ["/jJhqXTsAXCS46NhkYXBM7HDs8z8.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.7, "NumRating": 598},
+ {"MovieId": "290751", "Title": "Secret in Their Eyes", "Casts": [], "PlotId": "290751",
+ "ThumbnailIds": ["/jHaJkzzmxjwXnvNHBFqXd8l4UE4.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.3,
+ "NumRating": 682},
+ {"MovieId": "353326", "Title": "The Man Who Knew Infinity", "Casts": [], "PlotId": "353326",
+ "ThumbnailIds": ["/stXjVMZlY8khhjiJYrq4DdQ5uyV.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.2,
+ "NumRating": 685}, {"MovieId": "152780", "Title": "The Past", "Casts": [], "PlotId": "152780",
+ "ThumbnailIds": ["/kjerwTkZJev6Ve5DS0hRbaOuVJb.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.3, "NumRating": 247},
+ {"MovieId": "341006", "Title": "The Belko Experiment", "Casts": [], "PlotId": "341006",
+ "ThumbnailIds": ["/faJK0dP3S92kQoKtO4LZMjy41kf.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 743}, {"MovieId": "9703", "Title": "The Last Legion", "Casts": [], "PlotId": "9703",
+ "ThumbnailIds": ["/8K4WWwFew1CzCGVkgmKdamCA6kz.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.4, "NumRating": 393},
+ {"MovieId": "316152", "Title": "Free State of Jones", "Casts": [], "PlotId": "316152",
+ "ThumbnailIds": ["/tQcwB5nXpN4vH5ewP79tyXJcA1I.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 773}, {"MovieId": "97614", "Title": "Deadfall", "Casts": [], "PlotId": "97614",
+ "ThumbnailIds": ["/kt3bqW8pgbIxJY7aOqcQfUpB8dA.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.9, "NumRating": 352},
+ {"MovieId": "278", "Title": "The Shawshank Redemption", "Casts": [], "PlotId": "278",
+ "ThumbnailIds": ["/9O7gLzmreU0nGkIB6K3BsJbzvNv.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.7,
+ "NumRating": 12976}, {"MovieId": "351339", "Title": "Anthropoid", "Casts": [], "PlotId": "351339",
+ "ThumbnailIds": ["/5hUghRVVkBYufftfNQkGevY5AmE.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.0, "NumRating": 504},
+ {"MovieId": "353616", "Title": "Pitch Perfect 3", "Casts": [], "PlotId": "353616",
+ "ThumbnailIds": ["/fchHLsLjFvzAFSQykiMwdF1051.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.5,
+ "NumRating": 1834},
+ {"MovieId": "77948", "Title": "The Cold Light of Day", "Casts": [], "PlotId": "77948",
+ "ThumbnailIds": ["/zXhphNKS56VQbVJXqk3OMrjtNNc.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.1,
+ "NumRating": 454}, {"MovieId": "1667", "Title": "March of the Penguins", "Casts": [], "PlotId": "1667",
+ "ThumbnailIds": ["/yjAayG6nY9VO8IJoTLrSLUyu5kD.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.0, "NumRating": 593},
+ {"MovieId": "341012", "Title": "Popstar: Never Stop Never Stopping", "Casts": [], "PlotId": "341012",
+ "ThumbnailIds": ["/m9k2MCvZJWkV5DfJY9fueeeqNxb.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 662}, {"MovieId": "15370", "Title": "The Cat Returns", "Casts": [], "PlotId": "15370",
+ "ThumbnailIds": ["/57rS5DXN8YfRHgh609RgCKjKbel.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.2, "NumRating": 698},
+ {"MovieId": "289222", "Title": "The Zookeeper's Wife", "Casts": [], "PlotId": "289222",
+ "ThumbnailIds": ["/50KGpMiIvSkF4WHOgp0gM6r6sMU.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 705}, {"MovieId": "420817", "Title": "Aladdin", "Casts": [], "PlotId": "420817",
+ "ThumbnailIds": ["/3iYQTLGoy7QnjcUYRJy4YrAgGvp.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 0.0, "NumRating": 0},
+ {"MovieId": "395458", "Title": "Suburbicon", "Casts": [], "PlotId": "395458",
+ "ThumbnailIds": ["/a3IHgSwO5jWPLcGjKqbQ7pxVGkq.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.8,
+ "NumRating": 859}, {"MovieId": "54318", "Title": "The Water Horse", "Casts": [], "PlotId": "54318",
+ "ThumbnailIds": ["/i1DU0Nux6CozY1uEjyz5uCWBxhc.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.4, "NumRating": 518},
+ {"MovieId": "27205", "Title": "Inception", "Casts": [], "PlotId": "27205",
+ "ThumbnailIds": ["/qmDpIHrmpJINaRKAfWQfftjCdyi.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.3,
+ "NumRating": 21844},
+ {"MovieId": "10925", "Title": "The Return of the Living Dead", "Casts": [], "PlotId": "10925",
+ "ThumbnailIds": ["/1vydUitzC8W8W5oNevEArCltydJ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 465}, {"MovieId": "179826", "Title": "Odd Thomas", "Casts": [], "PlotId": "179826",
+ "ThumbnailIds": ["/b8FUEQnuig1BpBiWVfybOk3VozN.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 539},
+ {"MovieId": "390062", "Title": "Jungle", "Casts": [], "PlotId": "390062",
+ "ThumbnailIds": ["/tDgxknTVwrScxpCYyGUjXSn5NRk.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 800}, {"MovieId": "680", "Title": "Pulp Fiction", "Casts": [], "PlotId": "680",
+ "ThumbnailIds": ["/dM2w364MScsjFf8pfMbaWUcWrR.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.4, "NumRating": 14963},
+ {"MovieId": "2019", "Title": "Hard Target", "Casts": [], "PlotId": "2019",
+ "ThumbnailIds": ["/6WEu60V7EzncuFJSVmGJzhFvs4I.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 407},
+ {"MovieId": "302349", "Title": "Iron Sky: The Coming Race", "Casts": [], "PlotId": "302349",
+ "ThumbnailIds": ["/l5t2Nf1F7iQUKTrODg93xmQzZLj.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.8,
+ "NumRating": 16}, {"MovieId": "280", "Title": "Terminator 2: Judgment Day", "Casts": [], "PlotId": "280",
+ "ThumbnailIds": ["/2y4dmgWYRMYXdD1UyJVcn2HSd1D.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.9, "NumRating": 6589},
+ {"MovieId": "11976", "Title": "Legend", "Casts": [], "PlotId": "11976",
+ "ThumbnailIds": ["/xNWs9LM46vJWwf1q0VGPOccTKg4.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 496}, {"MovieId": "8966", "Title": "Twilight", "Casts": [], "PlotId": "8966",
+ "ThumbnailIds": ["/lcMp3AONdNhjYE9MmTtMMTOiRDP.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.0, "NumRating": 7194},
+ {"MovieId": "10048", "Title": "Stealth", "Casts": [], "PlotId": "10048",
+ "ThumbnailIds": ["/tiezxIq6TJO4B09c13Z8a675dDy.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.1,
+ "NumRating": 492}, {"MovieId": "369192", "Title": "Battle of the Sexes", "Casts": [], "PlotId": "369192",
+ "ThumbnailIds": ["/fWy0A3VojTCb0S2MKtEJjpquubF.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.9, "NumRating": 1022},
+ {"MovieId": "381288", "Title": "Split", "Casts": [], "PlotId": "381288",
+ "ThumbnailIds": ["/rXMWOZiCt6eMX22jWuTOSdQ98bY.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.2,
+ "NumRating": 10125}, {"MovieId": "111190", "Title": "Adore", "Casts": [], "PlotId": "111190",
+ "ThumbnailIds": ["/l2OTtTGfSfBRKArvzn14sk3xiNL.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.1, "NumRating": 547},
+ {"MovieId": "12103", "Title": "Don't Say a Word", "Casts": [], "PlotId": "12103",
+ "ThumbnailIds": ["/qx3hgW9MqxsEEFjx4eSbpp1Fe2l.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 402}, {"MovieId": "88273", "Title": "A Royal Affair", "Casts": [], "PlotId": "88273",
+ "ThumbnailIds": ["/f7bfKSEasjNLJ7I8rS5YJmzSDGr.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.4, "NumRating": 442},
+ {"MovieId": "5336", "Title": "Sal\u00f2, or the 120 Days of Sodom", "Casts": [], "PlotId": "5336",
+ "ThumbnailIds": ["/xnaDdiRfZlJaTf6JRc4in40eaeI.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 658}, {"MovieId": "399361", "Title": "Triple Frontier", "Casts": [], "PlotId": "399361",
+ "ThumbnailIds": ["/aBw8zYuAljVM1FeK5bZKITPH8ZD.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.2, "NumRating": 992},
+ {"MovieId": "11319", "Title": "The Rescuers", "Casts": [], "PlotId": "11319",
+ "ThumbnailIds": ["/49rGpB2x6AFB83SC4IBl9foRIGp.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 1264}, {"MovieId": "335984", "Title": "Blade Runner 2049", "Casts": [], "PlotId": "335984",
+ "ThumbnailIds": ["/gajva2L0rPYkEWjzgFlBXCAVBE5.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.4, "NumRating": 6549},
+ {"MovieId": "314405", "Title": "Tale of Tales", "Casts": [], "PlotId": "314405",
+ "ThumbnailIds": ["/9HKUonyvlNvW74rxX9XwbiWCjQV.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 827}, {"MovieId": "260346", "Title": "Taken 3", "Casts": [], "PlotId": "260346",
+ "ThumbnailIds": ["/ikDwR3i2bczqnRf1urJTy77YTFf.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.1, "NumRating": 3360},
+ {"MovieId": "10199", "Title": "The Tale of Despereaux", "Casts": [], "PlotId": "10199",
+ "ThumbnailIds": ["/8Nge4rXAQzU5w9U8OvnXuuJltL9.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.8,
+ "NumRating": 454},
+ {"MovieId": "338189", "Title": "It's Only the End of the World", "Casts": [], "PlotId": "338189",
+ "ThumbnailIds": ["/xTqy97V5EYtnMWWo2wVpScanpDS.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 845}, {"MovieId": "182560", "Title": "Dark Places", "Casts": [], "PlotId": "182560",
+ "ThumbnailIds": ["/1z7Bxnxi1lgO0ksc6peI4UssEPf.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.9, "NumRating": 747},
+ {"MovieId": "574241", "Title": "Poms", "Casts": [], "PlotId": "574241",
+ "ThumbnailIds": ["/m2ksKdmWIoUh3GJfu9oEBLorjAJ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.5,
+ "NumRating": 1},
+ {"MovieId": "438674", "Title": "Dragged Across Concrete", "Casts": [], "PlotId": "438674",
+ "ThumbnailIds": ["/fVG4a27ImyPS4vvNMjCtan3QhDl.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.8,
+ "NumRating": 95}, {"MovieId": "467660", "Title": "Unsane", "Casts": [], "PlotId": "467660",
+ "ThumbnailIds": ["/jvDBfavZASdKsJunu9VCAtXjLS2.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.1, "NumRating": 632},
+ {"MovieId": "2749", "Title": "15 Minutes", "Casts": [], "PlotId": "2749",
+ "ThumbnailIds": ["/qqqleHV8y7NLKt7isSZAvOwVPH6.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.7,
+ "NumRating": 321},
+ {"MovieId": "123025", "Title": "Batman: The Dark Knight Returns, Part 1", "Casts": [], "PlotId": "123025",
+ "ThumbnailIds": ["/mFPD2YsdaWAzjuxF7ItGmsEFpdY.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.7,
+ "NumRating": 691}, {"MovieId": "38543", "Title": "Ironclad", "Casts": [], "PlotId": "38543",
+ "ThumbnailIds": ["/kxAkA3cL0pSiGlW1JeiGHJr5Jv2.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.1, "NumRating": 366},
+ {"MovieId": "11045", "Title": "Taxi", "Casts": [], "PlotId": "11045",
+ "ThumbnailIds": ["/7Ly55SeRBozH3ZBeSOodcgKGYlF.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.9,
+ "NumRating": 434}, {"MovieId": "3093", "Title": "Basic Instinct 2", "Casts": [], "PlotId": "3093",
+ "ThumbnailIds": ["/gXzrKzHnMmfWCF5PMYQfNOzCxYl.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 4.7, "NumRating": 319},
+ {"MovieId": "315664", "Title": "Florence Foster Jenkins", "Casts": [], "PlotId": "315664",
+ "ThumbnailIds": ["/aBjQeNw7JBpXWGFaPYcGNlecRyr.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 837}, {"MovieId": "210577", "Title": "Gone Girl", "Casts": [], "PlotId": "210577",
+ "ThumbnailIds": ["/gdiLTof3rbPDAmPaCf4g6op46bj.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.9, "NumRating": 10253},
+ {"MovieId": "397422", "Title": "Rough Night", "Casts": [], "PlotId": "397422",
+ "ThumbnailIds": ["/i66xbL1C6FEWDm2KoX11DHmP4Rz.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.5,
+ "NumRating": 1053}, {"MovieId": "424", "Title": "Schindler's List", "Casts": [], "PlotId": "424",
+ "ThumbnailIds": ["/yPisjyLweCl1tbgwgtzBCNCBle.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.5, "NumRating": 7885},
+ {"MovieId": "218778", "Title": "Alexander and the Terrible, Horrible, No Good, Very Bad Day", "Casts": [],
+ "PlotId": "218778", "ThumbnailIds": ["/f29NQJaWyzXldOGT7F5CDKbwAH9.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.1, "NumRating": 801},
+ {"MovieId": "9994", "Title": "The Great Mouse Detective", "Casts": [], "PlotId": "9994",
+ "ThumbnailIds": ["/9uDr7vfjCFr39KGCcqrk44Cg7fQ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 746}, {"MovieId": "10320", "Title": "The Ring Two", "Casts": [], "PlotId": "10320",
+ "ThumbnailIds": ["/mYZdcd5XeZkye72oQejkLuAGI7q.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.6, "NumRating": 1219},
+ {"MovieId": "374475", "Title": "Toni Erdmann", "Casts": [], "PlotId": "374475",
+ "ThumbnailIds": ["/gJNjVE8WGUjiSKUtMDEvNzxR5zq.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.1,
+ "NumRating": 479}, {"MovieId": "830", "Title": "Forbidden Planet", "Casts": [], "PlotId": "830",
+ "ThumbnailIds": ["/qdH1Fm5vl1DAjXNHPmRZ4yEiUgM.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.3, "NumRating": 376},
+ {"MovieId": "243940", "Title": "The Lazarus Effect", "Casts": [], "PlotId": "243940",
+ "ThumbnailIds": ["/3oqjCN38pb7MI5i8lUlDQd4IAeA.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.1,
+ "NumRating": 809},
+ {"MovieId": "9662", "Title": "The Triplets of Belleville", "Casts": [], "PlotId": "9662",
+ "ThumbnailIds": ["/A8aSp63Xi4cnip3wAhk7Jml1uWG.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 437}, {"MovieId": "41211", "Title": "Heartbreaker", "Casts": [], "PlotId": "41211",
+ "ThumbnailIds": ["/fECaEIjjvozG1dGoi6cgTevIae5.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 743},
+ {"MovieId": "515248", "Title": "Someone Great", "Casts": [], "PlotId": "515248",
+ "ThumbnailIds": ["/h0nz5lIBXeUZChBNfwL08bLWQaU.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.8,
+ "NumRating": 328}, {"MovieId": "177494", "Title": "Veronica Mars", "Casts": [], "PlotId": "177494",
+ "ThumbnailIds": ["/nS3L07mQfcNJcisLEKgi8fWoBS1.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 695},
+ {"MovieId": "102780", "Title": "Byzantium", "Casts": [], "PlotId": "102780",
+ "ThumbnailIds": ["/pSnlpdh27luQ6GcchSkPomZ5fyr.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 465}, {"MovieId": "155084", "Title": "13 Sins", "Casts": [], "PlotId": "155084",
+ "ThumbnailIds": ["/uYPy89cVgvE1ic0bN39YJrumxwo.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.3, "NumRating": 561},
+ {"MovieId": "353081", "Title": "Mission: Impossible - Fallout", "Casts": [], "PlotId": "353081",
+ "ThumbnailIds": ["/AkJQpZp9WoNdj7pLYSj1L0RcMMN.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 3664},
+ {"MovieId": "250734", "Title": "Far from the Madding Crowd", "Casts": [], "PlotId": "250734",
+ "ThumbnailIds": ["/6v1DEaO2TZzrqcaGohUf1K7BXkU.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 578}, {"MovieId": "471859", "Title": "Charlie Says", "Casts": [], "PlotId": "471859",
+ "ThumbnailIds": ["/nwYDXLQpPULdUbwkTc4gWpMJufn.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 4.0, "NumRating": 5},
+ {"MovieId": "252178", "Title": "'71", "Casts": [], "PlotId": "252178",
+ "ThumbnailIds": ["/b8dmfG84peFdouN2N8wOsiI9WHt.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.8,
+ "NumRating": 617}, {"MovieId": "16608", "Title": "The Proposition", "Casts": [], "PlotId": "16608",
+ "ThumbnailIds": ["/e4j7s9SzaJTfjkorMX1iY38IzZi.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.2, "NumRating": 327},
+ {"MovieId": "127560", "Title": "The Railway Man", "Casts": [], "PlotId": "127560",
+ "ThumbnailIds": ["/3iU9KLIhvc3pdl1SkeD0gv4BO0f.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.9,
+ "NumRating": 400}, {"MovieId": "428449", "Title": "A Ghost Story", "Casts": [], "PlotId": "428449",
+ "ThumbnailIds": ["/rp5JPIyZi9sMob15l46zNQLe5cO.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.0, "NumRating": 879},
+ {"MovieId": "330483", "Title": "The Choice", "Casts": [], "PlotId": "330483",
+ "ThumbnailIds": ["/7EenqQdtZjOeBAAlxNtnG1eLHnf.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 1022},
+ {"MovieId": "10131", "Title": "A Nightmare on Elm Street 4: The Dream Master", "Casts": [],
+ "PlotId": "10131", "ThumbnailIds": ["/gERdob60xVv0tJ1Kr3XEqqj0KFl.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.8, "NumRating": 556},
+ {"MovieId": "10497", "Title": "Bitter Moon", "Casts": [], "PlotId": "10497",
+ "ThumbnailIds": ["/sUiqWj0k5oPcThd1WaXvt9Vymzr.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 231}, {"MovieId": "329865", "Title": "Arrival", "Casts": [], "PlotId": "329865",
+ "ThumbnailIds": ["/hLudzvGfpi6JlwUnsNhXwKKg4j.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.4, "NumRating": 10225},
+ {"MovieId": "55301", "Title": "Alvin and the Chipmunks: Chipwrecked", "Casts": [], "PlotId": "55301",
+ "ThumbnailIds": ["/s1nHXSJTq45RUHn7RmW6fO7kHu9.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.5,
+ "NumRating": 1028}, {"MovieId": "375315", "Title": "The Salesman", "Casts": [], "PlotId": "375315",
+ "ThumbnailIds": ["/sLqw0NXQJY8S8Na94rlefavBnRX.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.3, "NumRating": 380},
+ {"MovieId": "9425", "Title": "Soldier", "Casts": [], "PlotId": "9425",
+ "ThumbnailIds": ["/lpByPXb2xJvsmeEBxO9YPADLLdi.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 326},
+ {"MovieId": "121", "Title": "The Lord of the Rings: The Two Towers", "Casts": [], "PlotId": "121",
+ "ThumbnailIds": ["/5VTN0pR8gcqV3EPUHHfMGnJYN9L.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.3,
+ "NumRating": 12288},
+ {"MovieId": "400535", "Title": "Sicario: Day of the Soldado", "Casts": [], "PlotId": "400535",
+ "ThumbnailIds": ["/msqWSQkU403cQKjQHnWLnugv7EY.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 1261},
+ {"MovieId": "498248", "Title": "Mia and the White Lion", "Casts": [], "PlotId": "498248",
+ "ThumbnailIds": ["/zXfwBeYe0RnqsJ0dnBoFTB4SSrG.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.7,
+ "NumRating": 134}, {"MovieId": "429197", "Title": "Vice", "Casts": [], "PlotId": "429197",
+ "ThumbnailIds": ["/1gCab6rNv1r6V64cwsU4oEr649Y.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.1, "NumRating": 1095},
+ {"MovieId": "12444", "Title": "Harry Potter and the Deathly Hallows: Part 1", "Casts": [],
+ "PlotId": "12444", "ThumbnailIds": ["/maP4MTfPCeVD2FZbKTLUgriOW4R.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.7, "NumRating": 10591},
+ {"MovieId": "269149", "Title": "Zootopia", "Casts": [], "PlotId": "269149",
+ "ThumbnailIds": ["/sM33SANp9z6rXW8Itn7NnG1GOEs.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.7,
+ "NumRating": 9815}, {"MovieId": "22787", "Title": "Whiteout", "Casts": [], "PlotId": "22787",
+ "ThumbnailIds": ["/oC28DSfylqtkf8AWkr0cVj7TcjS.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.4, "NumRating": 348},
+ {"MovieId": "1930", "Title": "The Amazing Spider-Man", "Casts": [], "PlotId": "1930",
+ "ThumbnailIds": ["/eA2D86Y6VPWuUzZyatiLBwpTilQ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.5,
+ "NumRating": 10242}, {"MovieId": "9021", "Title": "The Santa Clause 2", "Casts": [], "PlotId": "9021",
+ "ThumbnailIds": ["/i7tbiDPIaa4VsQh1wWmbkY4zTRX.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.6, "NumRating": 609},
+ {"MovieId": "527641", "Title": "Five Feet Apart", "Casts": [], "PlotId": "527641",
+ "ThumbnailIds": ["/kreTuJBkUjVWePRfhHZuYfhNE1T.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.2,
+ "NumRating": 470},
+ {"MovieId": "140607", "Title": "Star Wars: The Force Awakens", "Casts": [], "PlotId": "140607",
+ "ThumbnailIds": ["/weUSwMdQIa3NaXVzwUoIIcAi85d.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 12473}, {"MovieId": "8342", "Title": "No Man's Land", "Casts": [], "PlotId": "8342",
+ "ThumbnailIds": ["/8DvLzDG9OeDzKdXZjRkCoWfXUcc.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.7, "NumRating": 184},
+ {"MovieId": "244786", "Title": "Whiplash", "Casts": [], "PlotId": "244786",
+ "ThumbnailIds": ["/lIv1QinFqz4dlp5U4lQ6HaiskOZ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.4,
+ "NumRating": 7750},
+ {"MovieId": "243938", "Title": "Hot Tub Time Machine 2", "Casts": [], "PlotId": "243938",
+ "ThumbnailIds": ["/tQtWuwvMf0hCc2QR2tkolwl7c3c.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.1,
+ "NumRating": 498}, {"MovieId": "419479", "Title": "The Babysitter", "Casts": [], "PlotId": "419479",
+ "ThumbnailIds": ["/86a7GRVRCwfl7wdI4QadyvKa6Zu.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.9, "NumRating": 1519},
+ {"MovieId": "82390", "Title": "The Paperboy", "Casts": [], "PlotId": "82390",
+ "ThumbnailIds": ["/foYlHgTIT8PFOeHOFxBICDZmYrm.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.5,
+ "NumRating": 398}, {"MovieId": "198663", "Title": "The Maze Runner", "Casts": [], "PlotId": "198663",
+ "ThumbnailIds": ["/coss7RgL0NH6g4fC2s5atvf3dFO.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.1, "NumRating": 10483},
+ {"MovieId": "11470", "Title": "Jason X", "Casts": [], "PlotId": "11470",
+ "ThumbnailIds": ["/zdfRa0FLVxfRwWhwitIZhjEIepI.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.5,
+ "NumRating": 525},
+ {"MovieId": "675", "Title": "Harry Potter and the Order of the Phoenix", "Casts": [], "PlotId": "675",
+ "ThumbnailIds": ["/4YnLxYLHhT4UQ8i9jxAXWy46Xuw.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.6,
+ "NumRating": 10605}, {"MovieId": "11050", "Title": "Terms of Endearment", "Casts": [], "PlotId": "11050",
+ "ThumbnailIds": ["/iZiYzBb6nyv2anbpVTV5ba6GSwq.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.1, "NumRating": 320},
+ {"MovieId": "109445", "Title": "Frozen", "Casts": [], "PlotId": "109445",
+ "ThumbnailIds": ["/eFnGmj63QPUpK7QUWSOUhypIQOT.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 9577},
+ {"MovieId": "11549", "Title": "Invasion of the Body Snatchers", "Casts": [], "PlotId": "11549",
+ "ThumbnailIds": ["/4oj0G2Zr6tQKBDOzKaLuefdeBsG.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.5,
+ "NumRating": 406}, {"MovieId": "38541", "Title": "The Divide", "Casts": [], "PlotId": "38541",
+ "ThumbnailIds": ["/3AdJMWYMjZBED2DLfBiOq7Rfw03.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.9, "NumRating": 300},
+ {"MovieId": "398175", "Title": "Brawl in Cell Block 99", "Casts": [], "PlotId": "398175",
+ "ThumbnailIds": ["/bfB1J6jsjdGWKjXxKQ5hNd1OyAs.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 489}, {"MovieId": "77663", "Title": "Killing Season", "Casts": [], "PlotId": "77663",
+ "ThumbnailIds": ["/ftZVLKyr3RDGtVelU06GAbtOFQa.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.5, "NumRating": 571},
+ {"MovieId": "673", "Title": "Harry Potter and the Prisoner of Azkaban", "Casts": [], "PlotId": "673",
+ "ThumbnailIds": ["/jUFjMoLh8T2CWzHUSjKCojI5SHu.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.9,
+ "NumRating": 11378}, {"MovieId": "332340", "Title": "Man Up", "Casts": [], "PlotId": "332340",
+ "ThumbnailIds": ["/y7C1EQ9zxJ3mlaQeRztw3NVw41P.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.6, "NumRating": 534},
+ {"MovieId": "458253", "Title": "Missing Link", "Casts": [], "PlotId": "458253",
+ "ThumbnailIds": ["/gEkKHiiQRVUSX15Iwo8VFydXrtu.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.2,
+ "NumRating": 39}, {"MovieId": "9725", "Title": "Friday the 13th Part 2", "Casts": [], "PlotId": "9725",
+ "ThumbnailIds": ["/92rGctBMTv4uaSlIBVnhz01kRWL.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.0, "NumRating": 618},
+ {"MovieId": "265189", "Title": "Force Majeure", "Casts": [], "PlotId": "265189",
+ "ThumbnailIds": ["/rGMtc9AtZsnWSSL5VnLaGvx1PI6.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.8,
+ "NumRating": 409},
+ {"MovieId": "101", "Title": "L\u00e9on: The Professional", "Casts": [], "PlotId": "101",
+ "ThumbnailIds": ["/gE8S02QUOhVnAmYu4tcrBlMTujz.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.3,
+ "NumRating": 7509}, {"MovieId": "233063", "Title": "Suck Me Shakespeer", "Casts": [], "PlotId": "233063",
+ "ThumbnailIds": ["/9GSGg4McYID6Ld0OMwimLr1Zx4J.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.1, "NumRating": 1051},
+ {"MovieId": "1497", "Title": "Teenage Mutant Ninja Turtles II: The Secret of the Ooze", "Casts": [],
+ "PlotId": "1497", "ThumbnailIds": ["/HD4LtUw4Ono9tzgwWOhHpHcjkj.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.0, "NumRating": 460},
+ {"MovieId": "309886", "Title": "Blood Father", "Casts": [], "PlotId": "309886",
+ "ThumbnailIds": ["/rQ3CKv33u2Z4keC2AYZqi3RGIdX.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 820}, {"MovieId": "31442", "Title": "Ivan's Childhood", "Casts": [], "PlotId": "31442",
+ "ThumbnailIds": ["/vmRWSLP1DE9WTta0hfzIafJ0dID.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.0, "NumRating": 221},
+ {"MovieId": "198375", "Title": "The Garden of Words", "Casts": [], "PlotId": "198375",
+ "ThumbnailIds": ["/f1Mhgu0sxvXEcUYDH4yVWdNh10j.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.5,
+ "NumRating": 830}, {"MovieId": "11497", "Title": "All Dogs Go to Heaven", "Casts": [], "PlotId": "11497",
+ "ThumbnailIds": ["/nmWh1NglDinfkHD9zCNqGWyhl7Q.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 468},
+ {"MovieId": "49051", "Title": "The Hobbit: An Unexpected Journey", "Casts": [], "PlotId": "49051",
+ "ThumbnailIds": ["/ysX7vDmSh5O19vFjAi56WL7l4nk.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.2,
+ "NumRating": 11926}, {"MovieId": "243683", "Title": "Step Up All In", "Casts": [], "PlotId": "243683",
+ "ThumbnailIds": ["/jFkvtmhE4NqENI4e0sZxmI8vrSS.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.8, "NumRating": 1187},
+ {"MovieId": "11596", "Title": "New Nightmare", "Casts": [], "PlotId": "11596",
+ "ThumbnailIds": ["/oLEZibnraixTE68rTaYv4FEmvYd.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 554}, {"MovieId": "223485", "Title": "Slow West", "Casts": [], "PlotId": "223485",
+ "ThumbnailIds": ["/uZ9dBBn0GaEw0YIBvQd01Io3pho.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 529},
+ {"MovieId": "524247", "Title": "The Intruder", "Casts": [], "PlotId": "524247",
+ "ThumbnailIds": ["/p9xKyetr0ihJ2K6HJMeXzc4IwEv.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.0,
+ "NumRating": 7}, {"MovieId": "453755", "Title": "Arctic", "Casts": [], "PlotId": "453755",
+ "ThumbnailIds": ["/dxRc0Y4IjSRLC2fYaguXQtJCI4e.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.2, "NumRating": 159},
+ {"MovieId": "6687", "Title": "Transsiberian", "Casts": [], "PlotId": "6687",
+ "ThumbnailIds": ["/izYDkKxj9sI8KNKK0PR54dpuiFu.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.5,
+ "NumRating": 320}, {"MovieId": "10523", "Title": "W.", "Casts": [], "PlotId": "10523",
+ "ThumbnailIds": ["/upFQuRzN23kLvzO1brSFClLqZvV.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.0, "NumRating": 238},
+ {"MovieId": "12246", "Title": "Mongol: The Rise of Genghis Khan", "Casts": [], "PlotId": "12246",
+ "ThumbnailIds": ["/8dlyyuwSJtS0VOLM9xi7SA7xbfU.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 271}, {"MovieId": "76544", "Title": "Man of Tai Chi", "Casts": [], "PlotId": "76544",
+ "ThumbnailIds": ["/42MutxsgeJS5pYLNWUGv8d6B9wa.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.0, "NumRating": 422},
+ {"MovieId": "93828", "Title": "Welcome to the Punch", "Casts": [], "PlotId": "93828",
+ "ThumbnailIds": ["/eJwSM618opISwPkzia9fNeFvW4Z.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.8,
+ "NumRating": 362},
+ {"MovieId": "227348", "Title": "Paranormal Activity: The Marked Ones", "Casts": [], "PlotId": "227348",
+ "ThumbnailIds": ["/fMC6CqCRDzQDxAibydeZF9XBAJ4.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.2,
+ "NumRating": 737}, {"MovieId": "205596", "Title": "The Imitation Game", "Casts": [], "PlotId": "205596",
+ "ThumbnailIds": ["/noUp0XOqIcmgefRnRZa1nhtRvWO.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.1, "NumRating": 10218},
+ {"MovieId": "10587", "Title": "Police Academy 4: Citizens on Patrol", "Casts": [], "PlotId": "10587",
+ "ThumbnailIds": ["/AcvfIPLXGxSucYGSmfkRU2SqLi4.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.3,
+ "NumRating": 486}, {"MovieId": "228150", "Title": "Fury", "Casts": [], "PlotId": "228150",
+ "ThumbnailIds": ["/pfte7wdMobMF4CVHuOxyu6oqeeA.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.4, "NumRating": 6541},
+ {"MovieId": "5902", "Title": "A Bridge Too Far", "Casts": [], "PlotId": "5902",
+ "ThumbnailIds": ["/ormdFwSHGVgsWdrzP5pRPaA6nme.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.2,
+ "NumRating": 329}, {"MovieId": "293310", "Title": "Citizenfour", "Casts": [], "PlotId": "293310",
+ "ThumbnailIds": ["/yNuLhb2y6I5cO7BfiJ7bdfllnIG.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.9, "NumRating": 700},
+ {"MovieId": "110420", "Title": "Wolf Children", "Casts": [], "PlotId": "110420",
+ "ThumbnailIds": ["/rDMxjCYEVnvLC4nsBpB6wjL0LDy.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.2,
+ "NumRating": 1024}, {"MovieId": "9962", "Title": "The Good Girl", "Casts": [], "PlotId": "9962",
+ "ThumbnailIds": ["/21AacCArwM5hQ8fYPRA0WKZviiN.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.0, "NumRating": 285},
+ {"MovieId": "466282", "Title": "To All the Boys I've Loved Before", "Casts": [], "PlotId": "466282",
+ "ThumbnailIds": ["/hKHZhUbIyUAjcSrqJThFGYIR6kI.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.9,
+ "NumRating": 4317}, {"MovieId": "439079", "Title": "The Nun", "Casts": [], "PlotId": "439079",
+ "ThumbnailIds": ["/sFC1ElvoKGdHJIWRpNB3xWJ9lJA.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.6, "NumRating": 2695},
+ {"MovieId": "246080", "Title": "Black Sea", "Casts": [], "PlotId": "246080",
+ "ThumbnailIds": ["/ghac5aKzS0tsmBhXXAWURcEEVk0.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.3,
+ "NumRating": 615}, {"MovieId": "535", "Title": "Flashdance", "Casts": [], "PlotId": "535",
+ "ThumbnailIds": ["/jwsnPftPybX2pTDbvWbyqviKO7S.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.4, "NumRating": 626},
+ {"MovieId": "11046", "Title": "Where Eagles Dare", "Casts": [], "PlotId": "11046",
+ "ThumbnailIds": ["/991u0tSWGTVXCYkgC6ftaGE4bkQ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.6,
+ "NumRating": 372}, {"MovieId": "10479", "Title": "Rules of Engagement", "Casts": [], "PlotId": "10479",
+ "ThumbnailIds": ["/1UlrMyCza2xINltCyYgmAwQAx07.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.1, "NumRating": 276},
+ {"MovieId": "526", "Title": "Ladyhawke", "Casts": [], "PlotId": "526",
+ "ThumbnailIds": ["/51RFCKLFuEbvLQsFzXcupQnkoRD.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 535}, {"MovieId": "345940", "Title": "The Meg", "Casts": [], "PlotId": "345940",
+ "ThumbnailIds": ["/eyWICPcxOuTcDDDbTMOZawoOn8d.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.9, "NumRating": 2764},
+ {"MovieId": "82702", "Title": "How to Train Your Dragon 2", "Casts": [], "PlotId": "82702",
+ "ThumbnailIds": ["/lRjOR4uclMQijUav4OjeZprlehu.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.7,
+ "NumRating": 5471},
+ {"MovieId": "767", "Title": "Harry Potter and the Half-Blood Prince", "Casts": [], "PlotId": "767",
+ "ThumbnailIds": ["/bFXys2nhALwDvpkF3dP3Vvdfn8b.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.6,
+ "NumRating": 10346}, {"MovieId": "8881", "Title": "Che: Part One", "Casts": [], "PlotId": "8881",
+ "ThumbnailIds": ["/eayI8CIR8miw3rDVLEwoqTL0TPt.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.8, "NumRating": 356},
+ {"MovieId": "157845", "Title": "The Rover", "Casts": [], "PlotId": "157845",
+ "ThumbnailIds": ["/ugVbrbicJHfOw8e24RUYISKqgvf.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 427},
+ {"MovieId": "503129", "Title": "Student of the Year 2", "Casts": [], "PlotId": "503129",
+ "ThumbnailIds": ["/ij8au3Sw1iw6AhroZ0AYYsCra51.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 3.0,
+ "NumRating": 3}, {"MovieId": "471507", "Title": "Destroyer", "Casts": [], "PlotId": "471507",
+ "ThumbnailIds": ["/sHw9gTdo43nJL82py0oaROkXXNr.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.8, "NumRating": 131},
+ {"MovieId": "150117", "Title": "I Give It a Year", "Casts": [], "PlotId": "150117",
+ "ThumbnailIds": ["/d05UThmFSAUD6FTHNjYxhrmdTec.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.4,
+ "NumRating": 483}, {"MovieId": "221902", "Title": "Two Days, One Night", "Casts": [], "PlotId": "221902",
+ "ThumbnailIds": ["/1mYAejpMskvskGr0J0SaBvdjmrH.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.9, "NumRating": 506},
+ {"MovieId": "433808", "Title": "The Ritual", "Casts": [], "PlotId": "433808",
+ "ThumbnailIds": ["/hHuJqzby593lmYmw1SzT0XYy99t.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 1054}, {"MovieId": "153158", "Title": "Underdogs", "Casts": [], "PlotId": "153158",
+ "ThumbnailIds": ["/rKnfNwbRGd6p7SidQYlGemCE8wb.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.1, "NumRating": 190},
+ {"MovieId": "209274", "Title": "Ida", "Casts": [], "PlotId": "209274",
+ "ThumbnailIds": ["/aO8dgaxaY1tcAdtFkLuz1WSxgdd.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 448},
+ {"MovieId": "58", "Title": "Pirates of the Caribbean: Dead Man's Chest", "Casts": [], "PlotId": "58",
+ "ThumbnailIds": ["/waFr5RVKaQ9dzOt3nQuIVB1FiPu.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.2,
+ "NumRating": 9232}, {"MovieId": "10972", "Title": "Session 9", "Casts": [], "PlotId": "10972",
+ "ThumbnailIds": ["/mWwrWhPHiHSoCWUkYcq4GplRF6V.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.2, "NumRating": 412},
+ {"MovieId": "168705", "Title": "BloodRayne", "Casts": [], "PlotId": "168705",
+ "ThumbnailIds": ["/qCrvpGxWjOh7tVLxyqWLR65ZuWX.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 3.8,
+ "NumRating": 207},
+ {"MovieId": "382322", "Title": "Batman: The Killing Joke", "Casts": [], "PlotId": "382322",
+ "ThumbnailIds": ["/zm0ODjtfJfJW0W269LqsQl5OhJ8.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.3,
+ "NumRating": 860}, {"MovieId": "3035", "Title": "Frankenstein", "Casts": [], "PlotId": "3035",
+ "ThumbnailIds": ["/wB58wlBAr6484Wm6VyFDqSD8VOJ.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.5, "NumRating": 591},
+ {"MovieId": "245916", "Title": "Kill the Messenger", "Casts": [], "PlotId": "245916",
+ "ThumbnailIds": ["/8gaNZiKZHvKCqMDByY00dUIV0YC.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 502}, {"MovieId": "84287", "Title": "The Imposter", "Casts": [], "PlotId": "84287",
+ "ThumbnailIds": ["/aj6kq3QskRkC1qJyau8ukB478Hw.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.4, "NumRating": 394},
+ {"MovieId": "300681", "Title": "Replicas", "Casts": [], "PlotId": "300681",
+ "ThumbnailIds": ["/hhPBTAn9b4TYOxc1JYNsX4BFAlW.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.7,
+ "NumRating": 239}, {"MovieId": "157851", "Title": "Maps to the Stars", "Casts": [], "PlotId": "157851",
+ "ThumbnailIds": ["/ciIFIMR78V9BMfLEylJGSwnlwm.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.0, "NumRating": 599},
+ {"MovieId": "2614", "Title": "Innerspace", "Casts": [], "PlotId": "2614",
+ "ThumbnailIds": ["/krRl7QKdIYsVUPYmOwElA1sqQE7.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 557}, {"MovieId": "12233", "Title": "Oliver & Company", "Casts": [], "PlotId": "12233",
+ "ThumbnailIds": ["/m54pXsIUy3IJoaGhk3RwKsZQPG8.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 759},
+ {"MovieId": "11128", "Title": "Ladder 49", "Casts": [], "PlotId": "11128",
+ "ThumbnailIds": ["/opgPMSdkXsTOB5W169VlivKyaZQ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.3,
+ "NumRating": 348},
+ {"MovieId": "408", "Title": "Snow White and the Seven Dwarfs", "Casts": [], "PlotId": "408",
+ "ThumbnailIds": ["/wbVGRBYPRRahIZNGXY9TfHDUSc2.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 4036},
+ {"MovieId": "2312", "Title": "In the Name of the King: A Dungeon Siege Tale", "Casts": [],
+ "PlotId": "2312", "ThumbnailIds": ["/bbN1lmDk1PT0GsTFCy179sk5nIF.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 4.4, "NumRating": 369},
+ {"MovieId": "74", "Title": "War of the Worlds", "Casts": [], "PlotId": "74",
+ "ThumbnailIds": ["/xXMM9KY2eq1SDOQif9zO91YOBA8.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.3,
+ "NumRating": 4330}, {"MovieId": "6522", "Title": "Life", "Casts": [], "PlotId": "6522",
+ "ThumbnailIds": ["/c9phL8FXuL7i15CrjQH5PfM60NB.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.5, "NumRating": 313},
+ {"MovieId": "11186", "Title": "Child's Play 2", "Casts": [], "PlotId": "11186",
+ "ThumbnailIds": ["/xy0qUntbDOVgiTDvSIFlSM3MMzP.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.9,
+ "NumRating": 663}, {"MovieId": "2322", "Title": "Sneakers", "Casts": [], "PlotId": "2322",
+ "ThumbnailIds": ["/pAGKtPF8nMiJq4lsINOYJWvMS2D.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.9, "NumRating": 440},
+ {"MovieId": "550", "Title": "Fight Club", "Casts": [], "PlotId": "550",
+ "ThumbnailIds": ["/adw6Lq9FiC9zjYEpOqfq03ituwp.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.4,
+ "NumRating": 16048},
+ {"MovieId": "10191", "Title": "How to Train Your Dragon", "Casts": [], "PlotId": "10191",
+ "ThumbnailIds": ["/ygGmAO60t8GyqUo9xYeYxSZAR3b.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.7,
+ "NumRating": 7446}, {"MovieId": "1534", "Title": "Pathfinder", "Casts": [], "PlotId": "1534",
+ "ThumbnailIds": ["/y3OvDjTiq7LdM6nDlZQbuEAFKou.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.4, "NumRating": 294},
+ {"MovieId": "454652", "Title": "Colette", "Casts": [], "PlotId": "454652",
+ "ThumbnailIds": ["/pGiUIkcTOEn2CwE5CUBFxWkcyxO.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.1,
+ "NumRating": 293}, {"MovieId": "157847", "Title": "Joe", "Casts": [], "PlotId": "157847",
+ "ThumbnailIds": ["/bvcLnoffCm67n7pZPVdJ6pluJsi.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 504},
+ {"MovieId": "13851", "Title": "Batman: Gotham Knight", "Casts": [], "PlotId": "13851",
+ "ThumbnailIds": ["/eIAhXROHG8t3QQ7qU0HfZgL5XFf.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 270}, {"MovieId": "5550", "Title": "RoboCop 3", "Casts": [], "PlotId": "5550",
+ "ThumbnailIds": ["/1YSXqKHjMAutWpIat9AOTIgozDb.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 4.5, "NumRating": 508},
+ {"MovieId": "11230", "Title": "Drunken Master", "Casts": [], "PlotId": "11230",
+ "ThumbnailIds": ["/fWrUTM8jeiU6G6rA4NKwkeigxGa.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.2,
+ "NumRating": 319},
+ {"MovieId": "9513", "Title": "Garfield: A Tail of Two Kitties", "Casts": [], "PlotId": "9513",
+ "ThumbnailIds": ["/aagx3t2Xv7R26hcqzrayTT28Yww.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.3,
+ "NumRating": 988}, {"MovieId": "571786", "Title": "Miss & Mrs. Cops", "Casts": [], "PlotId": "571786",
+ "ThumbnailIds": ["/v1sfULur2KiJIPGiXjGTDWNWxG.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 0.0, "NumRating": 0},
+ {"MovieId": "790", "Title": "The Fog", "Casts": [], "PlotId": "790",
+ "ThumbnailIds": ["/u9wAIHya0BHJvGJh3mBPr010U7C.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 572}, {"MovieId": "245698", "Title": "Pawn Sacrifice", "Casts": [], "PlotId": "245698",
+ "ThumbnailIds": ["/mxdpBuSMqql2Uvv27NIV1pahcsW.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 438},
+ {"MovieId": "345887", "Title": "The Equalizer 2", "Casts": [], "PlotId": "345887",
+ "ThumbnailIds": ["/cQvc9N6JiMVKqol3wcYrGshsIdZ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 1987},
+ {"MovieId": "12207", "Title": "The Legend of Drunken Master", "Casts": [], "PlotId": "12207",
+ "ThumbnailIds": ["/zZODgJHFtzs6wwa4cp6Nezb5AGe.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 373},
+ {"MovieId": "26123", "Title": "American Pie Presents: The Book of Love", "Casts": [], "PlotId": "26123",
+ "ThumbnailIds": ["/hwP0GEP0zy8ar965Xaht19SmMd3.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.1,
+ "NumRating": 900},
+ {"MovieId": "172", "Title": "Star Trek V: The Final Frontier", "Casts": [], "PlotId": "172",
+ "ThumbnailIds": ["/kugwPq2E5IkzrgoxRycnoqqUS9H.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.7,
+ "NumRating": 572}, {"MovieId": "36647", "Title": "Blade", "Casts": [], "PlotId": "36647",
+ "ThumbnailIds": ["/r0RQ9ZOEZglLOeYDNJTehVTRoR6.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 3083},
+ {"MovieId": "226486", "Title": "Tammy", "Casts": [], "PlotId": "226486",
+ "ThumbnailIds": ["/cq6wvDqETqJXgpQplkL0FBw2leM.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.2,
+ "NumRating": 719}, {"MovieId": "11967", "Title": "Young Guns", "Casts": [], "PlotId": "11967",
+ "ThumbnailIds": ["/wkGbwWHIM23BkmMBm7GQHaCZan8.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 405},
+ {"MovieId": "130150", "Title": "Labor Day", "Casts": [], "PlotId": "130150",
+ "ThumbnailIds": ["/4TKMiTqZnG6KJWjmxy2ZCDuG3M0.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.9,
+ "NumRating": 531},
+ {"MovieId": "12118", "Title": "Police Academy 3: Back in Training", "Casts": [], "PlotId": "12118",
+ "ThumbnailIds": ["/r4j14ZjmWfcQbboG76gxloqa3N3.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.7,
+ "NumRating": 492},
+ {"MovieId": "9064", "Title": "Hellbound: Hellraiser II", "Casts": [], "PlotId": "9064",
+ "ThumbnailIds": ["/ryuo4unQkzcEKhfAfzhbQo8oxVm.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 398}, {"MovieId": "1683", "Title": "The Reaping", "Casts": [], "PlotId": "1683",
+ "ThumbnailIds": ["/wwpI649fFYWaOhit5O1WLsFuHOI.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.5, "NumRating": 375},
+ {"MovieId": "517166", "Title": "The Axiom", "Casts": [], "PlotId": "517166",
+ "ThumbnailIds": ["/6lxM6WEXxJUFOsnHbWPIYnwiT0c.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 4}, {"MovieId": "11826", "Title": "Sexy Beast", "Casts": [], "PlotId": "11826",
+ "ThumbnailIds": ["/rpo9njGpJVLPqRrdM6R7wIqiQ7K.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.0, "NumRating": 354},
+ {"MovieId": "318846", "Title": "The Big Short", "Casts": [], "PlotId": "318846",
+ "ThumbnailIds": ["/p11Ftd4VposrAzthkhF53ifYZRl.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 4562},
+ {"MovieId": "74997", "Title": "The Human Centipede 2 (Full Sequence)", "Casts": [], "PlotId": "74997",
+ "ThumbnailIds": ["/kFtAdkCO0vXN2RWu2oMcR9GZ9Hi.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.6,
+ "NumRating": 580}, {"MovieId": "339380", "Title": "On the Basis of Sex", "Casts": [], "PlotId": "339380",
+ "ThumbnailIds": ["/izY9Le3QWtu7xkHq7bjJnuE5yGI.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.4, "NumRating": 187},
+ {"MovieId": "10756", "Title": "The Haunted Mansion", "Casts": [], "PlotId": "10756",
+ "ThumbnailIds": ["/lGi5yio4pdDz5PkSeZCbnMQz5vK.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.4,
+ "NumRating": 998}, {"MovieId": "24122", "Title": "The Rebound", "Casts": [], "PlotId": "24122",
+ "ThumbnailIds": ["/nkkFebe8ZgE843rnTrlaRWJy0g9.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.1, "NumRating": 393},
+ {"MovieId": "10016", "Title": "Ghosts of Mars", "Casts": [], "PlotId": "10016",
+ "ThumbnailIds": ["/rBmkaKxRg55zBZr11EGbedFJM0.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.0,
+ "NumRating": 483}, {"MovieId": "242582", "Title": "Nightcrawler", "Casts": [], "PlotId": "242582",
+ "ThumbnailIds": ["/8oPY6ULFOTbAEskySNhgsUIN4fW.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.7, "NumRating": 5760},
+ {"MovieId": "66", "Title": "Absolute Power", "Casts": [], "PlotId": "66",
+ "ThumbnailIds": ["/oJQdp09Oc51DkArsMDvgDLdWiDu.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 384}, {"MovieId": "304357", "Title": "Woman in Gold", "Casts": [], "PlotId": "304357",
+ "ThumbnailIds": ["/mNg0HRIhJyPgKo3NoxD3rfw061O.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.4, "NumRating": 780},
+ {"MovieId": "333339", "Title": "Ready Player One", "Casts": [], "PlotId": "333339",
+ "ThumbnailIds": ["/pU1ULUq8D3iRxl1fdX2lZIzdHuI.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.6,
+ "NumRating": 6795}, {"MovieId": "8984", "Title": "Disclosure", "Casts": [], "PlotId": "8984",
+ "ThumbnailIds": ["/qbwcjmHnXkeE8l7wNBsdnlrp3J4.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.0, "NumRating": 392},
+ {"MovieId": "10466", "Title": "The Money Pit", "Casts": [], "PlotId": "10466",
+ "ThumbnailIds": ["/vzUfCSAXDIQemW6Kdk2RU2JtfiV.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 468}, {"MovieId": "5511", "Title": "Le Samoura\u00ef", "Casts": [], "PlotId": "5511",
+ "ThumbnailIds": ["/axuBeLVBeXfVZPGg6ph2taWRDFq.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.0, "NumRating": 351},
+ {"MovieId": "4176", "Title": "Murder on the Orient Express", "Casts": [], "PlotId": "4176",
+ "ThumbnailIds": ["/66B9pHWl2KU7CwB3xcBNYvEo2CH.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.1,
+ "NumRating": 560}, {"MovieId": "286217", "Title": "The Martian", "Casts": [], "PlotId": "286217",
+ "ThumbnailIds": ["/5aGhaIHYuQbqlHWvWYqMCnj40y2.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.7, "NumRating": 12004},
+ {"MovieId": "331962", "Title": "Exposed", "Casts": [], "PlotId": "331962",
+ "ThumbnailIds": ["/nM26QosEfgjEegONCiNNrYMBTxD.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.6,
+ "NumRating": 243}, {"MovieId": "11866", "Title": "Flight of the Phoenix", "Casts": [], "PlotId": "11866",
+ "ThumbnailIds": ["/g1wvC9RyapnPQBgKcFJ3FChPEeC.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.9, "NumRating": 427},
+ {"MovieId": "606", "Title": "Out of Africa", "Casts": [], "PlotId": "606",
+ "ThumbnailIds": ["/gYNfg38sM4aSpxfC8gPkwg5UZHN.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 573}, {"MovieId": "191714", "Title": "The Lunchbox", "Casts": [], "PlotId": "191714",
+ "ThumbnailIds": ["/xFJqU1W5WlJiKr4Witnb7h9HNHn.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.3, "NumRating": 347},
+ {"MovieId": "87101", "Title": "Terminator Genisys", "Casts": [], "PlotId": "87101",
+ "ThumbnailIds": ["/5JU9ytZJyR3zmClGmVm9q4Geqbd.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.9,
+ "NumRating": 5248}, {"MovieId": "238636", "Title": "The Purge: Anarchy", "Casts": [], "PlotId": "238636",
+ "ThumbnailIds": ["/l1DRl40x2OWUoPP42v8fjKdS1Z3.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.6, "NumRating": 3694},
+ {"MovieId": "121875", "Title": "In the House", "Casts": [], "PlotId": "121875",
+ "ThumbnailIds": ["/4qAnWMNKUadx10jlCrkRAAuT4bN.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 416}, {"MovieId": "2275", "Title": "The General's Daughter", "Casts": [], "PlotId": "2275",
+ "ThumbnailIds": ["/4fc4mB9tGgYV4mdJrWPLMfcRvnC.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.2, "NumRating": 367},
+ {"MovieId": "396461", "Title": "Under the Silver Lake", "Casts": [], "PlotId": "396461",
+ "ThumbnailIds": ["/771Ey73LqsE9ORJhQCI25rgMXS2.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 305},
+ {"MovieId": "11418", "Title": "National Lampoon's European Vacation", "Casts": [], "PlotId": "11418",
+ "ThumbnailIds": ["/6AhVFxvDs2WZgfpC0bm2n2mshaa.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 365},
+ {"MovieId": "10437", "Title": "The Muppet Christmas Carol", "Casts": [], "PlotId": "10437",
+ "ThumbnailIds": ["/fe4nLJkDNXhyuMnrZ60MfoSpPes.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 415}, {"MovieId": "75802", "Title": "Hysteria", "Casts": [], "PlotId": "75802",
+ "ThumbnailIds": ["/1vtHZYTBS5iRA5ADibY6cwU7Ffm.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 448},
+ {"MovieId": "246655", "Title": "X-Men: Apocalypse", "Casts": [], "PlotId": "246655",
+ "ThumbnailIds": ["/zSouWWrySXshPCT4t3UKCQGayyo.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.5,
+ "NumRating": 8149}, {"MovieId": "10975", "Title": "Operation Condor", "Casts": [], "PlotId": "10975",
+ "ThumbnailIds": ["/loOps7gaDy1h6FTxQQdmh1L9TXn.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.1, "NumRating": 189},
+ {"MovieId": "338970", "Title": "Tomb Raider", "Casts": [], "PlotId": "338970",
+ "ThumbnailIds": ["/3zrC5tUiR35rTz9stuIxnU1nUS5.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.3,
+ "NumRating": 4176}, {"MovieId": "597", "Title": "Titanic", "Casts": [], "PlotId": "597",
+ "ThumbnailIds": ["/kHXEpyfl6zqn8a6YuozZUujufXf.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.8, "NumRating": 13986},
+ {"MovieId": "22586", "Title": "The Swan Princess", "Casts": [], "PlotId": "22586",
+ "ThumbnailIds": ["/oYp2bckWNBKOIbznjAfypVoMOFl.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 496}, {"MovieId": "10029", "Title": "Very Bad Things", "Casts": [], "PlotId": "10029",
+ "ThumbnailIds": ["/rGZ4bR7dNcAWntnVY5R8kge2IVq.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.3, "NumRating": 303},
+ {"MovieId": "245168", "Title": "Suffragette", "Casts": [], "PlotId": "245168",
+ "ThumbnailIds": ["/gG2Y8GNXzMrZCx65a8EhdNq2iuu.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 961}, {"MovieId": "1975", "Title": "The Grudge 2", "Casts": [], "PlotId": "1975",
+ "ThumbnailIds": ["/lPN7FyCuPDHwoupcuPT72cRE8lY.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.4, "NumRating": 486},
+ {"MovieId": "273477", "Title": "Scouts Guide to the Zombie Apocalypse", "Casts": [], "PlotId": "273477",
+ "ThumbnailIds": ["/3wHxtemithpFQiB7ffa50ZKI6bz.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 900}, {"MovieId": "8392", "Title": "My Neighbor Totoro", "Casts": [], "PlotId": "8392",
+ "ThumbnailIds": ["/2i0OOjvi7CqNQA6ZtYJtL65P9oZ.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.1, "NumRating": 3193},
+ {"MovieId": "86597", "Title": "The Tall Man", "Casts": [], "PlotId": "86597",
+ "ThumbnailIds": ["/1fc87E3ppCq6xBnZJ63Sa9FKRUd.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.9,
+ "NumRating": 558}, {"MovieId": "10648", "Title": "Magnum Force", "Casts": [], "PlotId": "10648",
+ "ThumbnailIds": ["/3gqV4jpKNFqxUWug3BRD6yUzSL1.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.1, "NumRating": 411},
+ {"MovieId": "559", "Title": "Spider-Man 3", "Casts": [], "PlotId": "559",
+ "ThumbnailIds": ["/2N9lhZg6VtVJoGCZDjXVC3a81Ea.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 6969},
+ {"MovieId": "527729", "Title": "Asterix: The Secret of the Magic Potion", "Casts": [], "PlotId": "527729",
+ "ThumbnailIds": ["/wmMq5ypRNJbWpdhC9aPjpdx1MMp.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 341}, {"MovieId": "10052", "Title": "Dragonfly", "Casts": [], "PlotId": "10052",
+ "ThumbnailIds": ["/gs3fFBoiEaExy5x3m5yAQpmVhXs.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.5, "NumRating": 370},
+ {"MovieId": "397", "Title": "French Kiss", "Casts": [], "PlotId": "397",
+ "ThumbnailIds": ["/bZqvonwVru1XtnniQZgZRa3Znyk.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 331}, {"MovieId": "4477", "Title": "The Devil's Own", "Casts": [], "PlotId": "4477",
+ "ThumbnailIds": ["/87ObtgniFhhPgESlIl85MfawduY.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.0, "NumRating": 502},
+ {"MovieId": "150689", "Title": "Cinderella", "Casts": [], "PlotId": "150689",
+ "ThumbnailIds": ["/2i0JH5WqYFqki7WDhUW56Sg0obh.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 4437}, {"MovieId": "44114", "Title": "3some", "Casts": [], "PlotId": "44114",
+ "ThumbnailIds": ["/bUaAybF88Gm9eKrcjB0lewx55Y3.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.0, "NumRating": 14},
+ {"MovieId": "4978", "Title": "An American Tail", "Casts": [], "PlotId": "4978",
+ "ThumbnailIds": ["/glZNfxN4cef0pJeD08xru7ZVWlI.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 649}, {"MovieId": "11880", "Title": "Dog Soldiers", "Casts": [], "PlotId": "11880",
+ "ThumbnailIds": ["/kJLGyrjnphk6DnaJhYVFFoWJcCy.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 346},
+ {"MovieId": "9754", "Title": "Firewall", "Casts": [], "PlotId": "9754",
+ "ThumbnailIds": ["/bp0BTphk7bgBof20XAfKkujRxwI.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.7,
+ "NumRating": 440}, {"MovieId": "320288", "Title": "Dark Phoenix", "Casts": [], "PlotId": "320288",
+ "ThumbnailIds": ["/kZv92eTc0Gg3mKxqjjDAM73z9cy.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 0.0, "NumRating": 0},
+ {"MovieId": "9538", "Title": "Scanners", "Casts": [], "PlotId": "9538",
+ "ThumbnailIds": ["/gl7uT1nm7kpi4Fv0YMgv2C1dGj3.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.8,
+ "NumRating": 441}, {"MovieId": "9530", "Title": "RV", "Casts": [], "PlotId": "9530",
+ "ThumbnailIds": ["/eqV0JjfwcEJuK3JPZ2rsNvS1p30.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.7, "NumRating": 888},
+ {"MovieId": "84165", "Title": "2 Days in New York", "Casts": [], "PlotId": "84165",
+ "ThumbnailIds": ["/7Zo1oQ7ssDfT3OKUAWqu9TD4J0H.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.6,
+ "NumRating": 148}, {"MovieId": "80038", "Title": "Friends with Kids", "Casts": [], "PlotId": "80038",
+ "ThumbnailIds": ["/ojCTu5O2scmRjlX26eS3EZbhjPb.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.8, "NumRating": 257},
+ {"MovieId": "2623", "Title": "An Officer and a Gentleman", "Casts": [], "PlotId": "2623",
+ "ThumbnailIds": ["/1fveHeEaOpGW91ZM4ViT0Jviuak.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.8,
+ "NumRating": 441}, {"MovieId": "10481", "Title": "102 Dalmatians", "Casts": [], "PlotId": "10481",
+ "ThumbnailIds": ["/dSxnIika9yWwTvEbpsmoGdeh65E.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.4, "NumRating": 678},
+ {"MovieId": "136", "Title": "Freaks", "Casts": [], "PlotId": "136",
+ "ThumbnailIds": ["/9hmmE4K6tOEXB1KeTajIC0pNta6.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.8,
+ "NumRating": 426}, {"MovieId": "135397", "Title": "Jurassic World", "Casts": [], "PlotId": "135397",
+ "ThumbnailIds": ["/jjBgi2r5cRt36xF6iNUEhzscEcb.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 13870},
+ {"MovieId": "10934", "Title": "Under the Tuscan Sun", "Casts": [], "PlotId": "10934",
+ "ThumbnailIds": ["/z5mqOKxS8R9mnzyRulIbMX0U6kG.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 329}, {"MovieId": "178809", "Title": "The Colony", "Casts": [], "PlotId": "178809",
+ "ThumbnailIds": ["/oYeCfvBSdIfxGSlSXMwtAUzKiJC.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.2, "NumRating": 624},
+ {"MovieId": "198185", "Title": "Million Dollar Arm", "Casts": [], "PlotId": "198185",
+ "ThumbnailIds": ["/rYOQL42cDdsbhfgaxeEW4SlszUp.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.8,
+ "NumRating": 348}, {"MovieId": "11782", "Title": "Hard Boiled", "Casts": [], "PlotId": "11782",
+ "ThumbnailIds": ["/8CfUC5QPTNe5NHlXWvYzpKIl3xs.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.6, "NumRating": 290},
+ {"MovieId": "3604", "Title": "Flash Gordon", "Casts": [], "PlotId": "3604",
+ "ThumbnailIds": ["/9c5vKUX7MB1yixVp0ZFAUDRTaeE.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 398}, {"MovieId": "9303", "Title": "Bound", "Casts": [], "PlotId": "9303",
+ "ThumbnailIds": ["/xfITNjW2sunPiB7BNotJJsCxhdA.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.0, "NumRating": 341},
+ {"MovieId": "143", "Title": "All Quiet on the Western Front", "Casts": [], "PlotId": "143",
+ "ThumbnailIds": ["/yAU6jklJLUjZot3WyvyJrxVdLKb.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.8,
+ "NumRating": 290}, {"MovieId": "336004", "Title": "Heist", "Casts": [], "PlotId": "336004",
+ "ThumbnailIds": ["/t5tGykRvvlLBULIPsAJEzGg1ylm.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.8, "NumRating": 492},
+ {"MovieId": "10057", "Title": "The Three Musketeers", "Casts": [], "PlotId": "10057",
+ "ThumbnailIds": ["/mk8UH7JRmK8adcqJJpB1ygP7B1C.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.3,
+ "NumRating": 385}, {"MovieId": "4437", "Title": "2010", "Casts": [], "PlotId": "4437",
+ "ThumbnailIds": ["/9Rcz2n16HEYRi2EKGliByP6ESYR.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 426},
+ {"MovieId": "3063", "Title": "Duck Soup", "Casts": [], "PlotId": "3063",
+ "ThumbnailIds": ["/jy4DJN8pKmEz4UNjCGiFaJuMfDJ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.6,
+ "NumRating": 320}, {"MovieId": "457136", "Title": "Mary Queen of Scots", "Casts": [], "PlotId": "457136",
+ "ThumbnailIds": ["/b5RMzLAyq5QW6GtN9sIeAEMLlBI.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 530},
+ {"MovieId": "41602", "Title": "Charlie Countryman", "Casts": [], "PlotId": "41602",
+ "ThumbnailIds": ["/jG95rLdI1V1pTEtAwSkKZR74AGm.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 364}, {"MovieId": "333385", "Title": "Mr. Right", "Casts": [], "PlotId": "333385",
+ "ThumbnailIds": ["/y1VT2NoBOx3aC2exhkyN9AGUkMR.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.4, "NumRating": 727},
+ {"MovieId": "9992", "Title": "Arthur and the Invisibles", "Casts": [], "PlotId": "9992",
+ "ThumbnailIds": ["/idbP413bzKWrQXJyElrDazcJUFM.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 1440},
+ {"MovieId": "1494", "Title": "Curse of the Golden Flower", "Casts": [], "PlotId": "1494",
+ "ThumbnailIds": ["/8JGOTkKOCqSfAwIMPQKF0K44lUW.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 346}, {"MovieId": "172533", "Title": "Drinking Buddies", "Casts": [], "PlotId": "172533",
+ "ThumbnailIds": ["/zongyslIHQmfnf9rgUioPkDaHmq.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.0, "NumRating": 522},
+ {"MovieId": "674", "Title": "Harry Potter and the Goblet of Fire", "Casts": [], "PlotId": "674",
+ "ThumbnailIds": ["/6sASqcdrEHXxUhA3nFpjrRecPD2.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.7,
+ "NumRating": 10908}, {"MovieId": "12192", "Title": "Pathology", "Casts": [], "PlotId": "12192",
+ "ThumbnailIds": ["/7VjVRhlM7GyqwTc3OH8Itfx4jaB.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.7, "NumRating": 207},
+ {"MovieId": "1825", "Title": "Over the Top", "Casts": [], "PlotId": "1825",
+ "ThumbnailIds": ["/rnAxGFrFaHemdjjFDQDtjOzV8z5.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 664}, {"MovieId": "8587", "Title": "The Lion King", "Casts": [], "PlotId": "8587",
+ "ThumbnailIds": ["/bKPtXn9n4M4s8vvZrbw40mYsefB.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.2, "NumRating": 9974},
+ {"MovieId": "10477", "Title": "Driven", "Casts": [], "PlotId": "10477",
+ "ThumbnailIds": ["/vtPvxgQBoNBFnnnYjS1lb6pLXtv.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.7,
+ "NumRating": 315},
+ {"MovieId": "131634", "Title": "The Hunger Games: Mockingjay - Part 2", "Casts": [], "PlotId": "131634",
+ "ThumbnailIds": ["/w93GAiq860UjmgR6tU9h2T24vaV.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.8,
+ "NumRating": 7485},
+ {"MovieId": "324552", "Title": "John Wick: Chapter 2", "Casts": [], "PlotId": "324552",
+ "ThumbnailIds": ["/zkXnKIwX5pYorKJp2fjFSfNyKT0.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 5487},
+ {"MovieId": "6279", "Title": "Sister Act 2: Back in the Habit", "Casts": [], "PlotId": "6279",
+ "ThumbnailIds": ["/tLbvfUYw8Pr7DMI5TGL5DX1bx5S.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 811}, {"MovieId": "24150", "Title": "Halloween II", "Casts": [], "PlotId": "24150",
+ "ThumbnailIds": ["/vSHPM4LQDpWdQrD5KZWK6wNqSOD.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.2, "NumRating": 555},
+ {"MovieId": "564394", "Title": "Crypto", "Casts": [], "PlotId": "564394", "ThumbnailIds": [None],
+ "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0, "NumRating": 0},
+ {"MovieId": "435577", "Title": "The 12th Man", "Casts": [], "PlotId": "435577",
+ "ThumbnailIds": ["/dxZ8iGf5jQoLcNqBJL6uzNTivp.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.6,
+ "NumRating": 118}, {"MovieId": "87499", "Title": "The East", "Casts": [], "PlotId": "87499",
+ "ThumbnailIds": ["/nGoFIX5WmthZZ3eLBd0QwDSkWNy.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 433},
+ {"MovieId": "443055", "Title": "Love of My Life", "Casts": [], "PlotId": "443055",
+ "ThumbnailIds": ["/7b19Sh0Aef5vGa0OFtvJxLe2SK9.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.4,
+ "NumRating": 8}, {"MovieId": "238", "Title": "The Godfather", "Casts": [], "PlotId": "238",
+ "ThumbnailIds": ["/rPdtLWNsZmAtoZl9PK7S2wE3qiS.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.6, "NumRating": 9858},
+ {"MovieId": "8869", "Title": "Eight Legged Freaks", "Casts": [], "PlotId": "8869",
+ "ThumbnailIds": ["/udEgrVBvcXV5tGdozogGsOpD1Rs.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.4,
+ "NumRating": 504}, {"MovieId": "4964", "Title": "Knocked Up", "Casts": [], "PlotId": "4964",
+ "ThumbnailIds": ["/b4OaXw2MW97VvIiZE0Sbn1NfxSh.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.2, "NumRating": 2118},
+ {"MovieId": "49017", "Title": "Dracula Untold", "Casts": [], "PlotId": "49017",
+ "ThumbnailIds": ["/4oy4e0DP6LRwRszfx8NY8EYBj8V.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 3679}, {"MovieId": "11527", "Title": "Excalibur", "Casts": [], "PlotId": "11527",
+ "ThumbnailIds": ["/j8UmbdA1TrIVY4FANymwBSmUuCH.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.1, "NumRating": 431},
+ {"MovieId": "39210", "Title": "Somewhere", "Casts": [], "PlotId": "39210",
+ "ThumbnailIds": ["/d3A0bbRp6sHN4uz6n3ddbtyL1tt.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 370},
+ {"MovieId": "166426", "Title": "Pirates of the Caribbean: Dead Men Tell No Tales", "Casts": [],
+ "PlotId": "166426", "ThumbnailIds": ["/xbpSDU3p7YUGlu9Mr6Egg2Vweto.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 6804},
+ {"MovieId": "12445", "Title": "Harry Potter and the Deathly Hallows: Part 2", "Casts": [],
+ "PlotId": "12445", "ThumbnailIds": ["/fTplI1NCSuEDP4ITLcTps739fcC.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.1, "NumRating": 11352},
+ {"MovieId": "8689", "Title": "Cannibal Holocaust", "Casts": [], "PlotId": "8689",
+ "ThumbnailIds": ["/r0VmAFHWAqlaraVg0krIEhDCPWH.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 612}, {"MovieId": "22949", "Title": "Old Dogs", "Casts": [], "PlotId": "22949",
+ "ThumbnailIds": ["/2B4tOJ71vJQktyjsG0iqGM0Yh10.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.3, "NumRating": 351},
+ {"MovieId": "156022", "Title": "The Equalizer", "Casts": [], "PlotId": "156022",
+ "ThumbnailIds": ["/2eQfjqlvPAxd9aLDs8DvsKLnfed.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.2,
+ "NumRating": 4883}, {"MovieId": "190469", "Title": "Redirected", "Casts": [], "PlotId": "190469",
+ "ThumbnailIds": ["/dbmXS7kEJxhAqVVmm5vpmDYpiqh.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.8, "NumRating": 211},
+ {"MovieId": "18148", "Title": "Tokyo Story", "Casts": [], "PlotId": "18148",
+ "ThumbnailIds": ["/g2YbTYKpY7N2yDSk7BfXZ18I5QV.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.3,
+ "NumRating": 338},
+ {"MovieId": "10539", "Title": "James and the Giant Peach", "Casts": [], "PlotId": "10539",
+ "ThumbnailIds": ["/kyAl5UfUtGJC1wHrPVieqKKCpn8.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 635}, {"MovieId": "11932", "Title": "Bride of Chucky", "Casts": [], "PlotId": "11932",
+ "ThumbnailIds": ["/u5Lc1Li0Hpc452o57E2KaToezZX.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.6, "NumRating": 702},
+ {"MovieId": "103620", "Title": "Maniac", "Casts": [], "PlotId": "103620",
+ "ThumbnailIds": ["/ag1IgAqYartblOy0IiDIMNoJUVI.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 461}, {"MovieId": "10163", "Title": "The Lawnmower Man", "Casts": [], "PlotId": "10163",
+ "ThumbnailIds": ["/3tWLM3zMyh3KZOactn8mfjHml05.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.4, "NumRating": 317},
+ {"MovieId": "9815", "Title": "Goal! II: Living the Dream", "Casts": [], "PlotId": "9815",
+ "ThumbnailIds": ["/sKKej989oVbZahldAkrMVmyB8pU.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.8,
+ "NumRating": 313},
+ {"MovieId": "295699", "Title": "Everybody Wants Some!!", "Casts": [], "PlotId": "295699",
+ "ThumbnailIds": ["/mIpd0rGxruvxCnMSmh4gd8wuhmv.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 669}, {"MovieId": "9289", "Title": "The Longest Day", "Casts": [], "PlotId": "9289",
+ "ThumbnailIds": ["/7fnuirXJpuRHggi2lOCBEwZ3eWU.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.5, "NumRating": 406},
+ {"MovieId": "177572", "Title": "Big Hero 6", "Casts": [], "PlotId": "177572",
+ "ThumbnailIds": ["/9gLu47Zw5ertuFTZaxXOvNfy78T.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.8,
+ "NumRating": 10037}, {"MovieId": "25643", "Title": "Love Happens", "Casts": [], "PlotId": "25643",
+ "ThumbnailIds": ["/pN51u0l8oSEsxAYiHUzzbMrMXH7.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.9, "NumRating": 345},
+ {"MovieId": "11212", "Title": "Baby's Day Out", "Casts": [], "PlotId": "11212",
+ "ThumbnailIds": ["/21U2jwl36hoTHsXB3fDuIQkcchu.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.9,
+ "NumRating": 565}, {"MovieId": "65759", "Title": "Happy Feet Two", "Casts": [], "PlotId": "65759",
+ "ThumbnailIds": ["/2gWiQ4mn85jcXtREVePlVViupeV.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.9, "NumRating": 736},
+ {"MovieId": "38234", "Title": "Undisputed III: Redemption", "Casts": [], "PlotId": "38234",
+ "ThumbnailIds": ["/vLSnWLCnqk5j3oLnMnBHJGL3bO0.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 296}, {"MovieId": "5375", "Title": "Fred Claus", "Casts": [], "PlotId": "5375",
+ "ThumbnailIds": ["/vFniWmciRi4tAjAYGp2wrK0P8dJ.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.5, "NumRating": 406},
+ {"MovieId": "8409", "Title": "A Man Apart", "Casts": [], "PlotId": "8409",
+ "ThumbnailIds": ["/l9UIm6rCHzfbMy6KY8ynjV4kLHX.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.9,
+ "NumRating": 406}, {"MovieId": "8090", "Title": "Untraceable", "Casts": [], "PlotId": "8090",
+ "ThumbnailIds": ["/ySUwDRDEn01lKIMPQorpFCMLWqE.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.0, "NumRating": 392},
+ {"MovieId": "93", "Title": "Anatomy of a Murder", "Casts": [], "PlotId": "93",
+ "ThumbnailIds": ["/kDFnM2zlHZirR2ItTo56lyrxuAS.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.9,
+ "NumRating": 347}, {"MovieId": "553100", "Title": "Wild and Free", "Casts": [], "PlotId": "553100",
+ "ThumbnailIds": ["/jLGNqaymD0ygyhafhv5fM3nXcge.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 10.0, "NumRating": 2},
+ {"MovieId": "200505", "Title": "Draft Day", "Casts": [], "PlotId": "200505",
+ "ThumbnailIds": ["/pb5FXL6pypVQbcs3TCzp5GqyTYr.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 454}, {"MovieId": "10610", "Title": "The Medallion", "Casts": [], "PlotId": "10610",
+ "ThumbnailIds": ["/9iJrg37ceCBeQkQHnIy97VekINb.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.3, "NumRating": 501},
+ {"MovieId": "16577", "Title": "Astro Boy", "Casts": [], "PlotId": "16577",
+ "ThumbnailIds": ["/4kQczIhUFTnDWwG6HKsgCxLoi6.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 706}, {"MovieId": "330947", "Title": "Song to Song", "Casts": [], "PlotId": "330947",
+ "ThumbnailIds": ["/rEvtGhNY2DQ4L8Ma6rpMhL6IbKM.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.3, "NumRating": 463},
+ {"MovieId": "514692", "Title": "Neon Heart", "Casts": [], "PlotId": "514692",
+ "ThumbnailIds": ["/RgniFwvK2vL3yapRrz4GdGZAnE.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0,
+ "NumRating": 0}, {"MovieId": "449985", "Title": "Triple Threat", "Casts": [], "PlotId": "449985",
+ "ThumbnailIds": ["/cSpM3QxmoSLp4O1WAMQpUDcaB7R.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.5, "NumRating": 75},
+ {"MovieId": "10731", "Title": "The Client", "Casts": [], "PlotId": "10731",
+ "ThumbnailIds": ["/bCWpxGGcP9DsCLwfNpnfcl1vLk8.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.5,
+ "NumRating": 500}, {"MovieId": "209276", "Title": "Starred Up", "Casts": [], "PlotId": "209276",
+ "ThumbnailIds": ["/cP5vXTItgkNahGEsaIHudbdIDRG.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.2, "NumRating": 521},
+ {"MovieId": "10623", "Title": "Cradle 2 the Grave", "Casts": [], "PlotId": "10623",
+ "ThumbnailIds": ["/v8iPcn54TNsSPabD9ZYQVQUWbXk.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.8,
+ "NumRating": 379}, {"MovieId": "9741", "Title": "Unbreakable", "Casts": [], "PlotId": "9741",
+ "ThumbnailIds": ["/pvL37V88plePxFSszCbV3wRHiBm.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.1, "NumRating": 4835},
+ {"MovieId": "862", "Title": "Toy Story", "Casts": [], "PlotId": "862",
+ "ThumbnailIds": ["/rhIRbceoE9lR4veEXuwCC2wARtG.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.9,
+ "NumRating": 9986}, {"MovieId": "505948", "Title": "I Am Mother", "Casts": [], "PlotId": "505948",
+ "ThumbnailIds": ["/eItrj5GcjvCI3oD3bIcz1A2IL9t.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 0.0, "NumRating": 1},
+ {"MovieId": "438740", "Title": "Salyut-7", "Casts": [], "PlotId": "438740",
+ "ThumbnailIds": ["/s2ktgF2ze4aVt9bXBUvxFJllqyd.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.9,
+ "NumRating": 146}, {"MovieId": "369972", "Title": "First Man", "Casts": [], "PlotId": "369972",
+ "ThumbnailIds": ["/i91mfvFcPPlaegcbOyjGgiWfZzh.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.1, "NumRating": 2180},
+ {"MovieId": "14536", "Title": "New in Town", "Casts": [], "PlotId": "14536",
+ "ThumbnailIds": ["/3C3WWizgIKWQ5O6k3xN5pkwXEYJ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.6,
+ "NumRating": 211}, {"MovieId": "10518", "Title": "Marathon Man", "Casts": [], "PlotId": "10518",
+ "ThumbnailIds": ["/uPNgubSiri2yvBQRPtP77ViYjN.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.4, "NumRating": 397},
+ {"MovieId": "9053", "Title": "DOA: Dead or Alive", "Casts": [], "PlotId": "9053",
+ "ThumbnailIds": ["/w1DbRfGKWWS3JR74q4vawHVQy5B.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.0,
+ "NumRating": 362}, {"MovieId": "426563", "Title": "Holmes & Watson", "Casts": [], "PlotId": "426563",
+ "ThumbnailIds": ["/orEUlKndjV1rEcWqXbbjegjfv97.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 4.2, "NumRating": 181},
+ {"MovieId": "9972", "Title": "Lock Up", "Casts": [], "PlotId": "9972",
+ "ThumbnailIds": ["/wRWZDNzebz2a52GtdhN1bx3ujE7.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.5,
+ "NumRating": 382}, {"MovieId": "4961", "Title": "Mimic", "Casts": [], "PlotId": "4961",
+ "ThumbnailIds": ["/tkmSgkekKleTx46a1ERny967Ws5.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.8, "NumRating": 418},
+ {"MovieId": "249", "Title": "The War of the Roses", "Casts": [], "PlotId": "249",
+ "ThumbnailIds": ["/9VWwYsuXhRImUtrJGvN6bYJB2He.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.8,
+ "NumRating": 499}, {"MovieId": "884", "Title": "Crash", "Casts": [], "PlotId": "884",
+ "ThumbnailIds": ["/4PDLGJsS5uVBQlYjLMZ7t85dU0P.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.5, "NumRating": 390},
+ {"MovieId": "9356", "Title": "Look Who's Talking Too", "Casts": [], "PlotId": "9356",
+ "ThumbnailIds": ["/7HtSFUYBPOUd9ylUXU0LyPsxpRm.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.2,
+ "NumRating": 655}, {"MovieId": "324670", "Title": "Spectral", "Casts": [], "PlotId": "324670",
+ "ThumbnailIds": ["/oXV2ayQYUQfHwpuMdWnZF0Geng5.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.3, "NumRating": 813},
+ {"MovieId": "1280", "Title": "3-Iron", "Casts": [], "PlotId": "1280",
+ "ThumbnailIds": ["/hr8ghKbdo3UGUROYafpN38Sohfe.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.8,
+ "NumRating": 444}, {"MovieId": "8879", "Title": "Pale Rider", "Casts": [], "PlotId": "8879",
+ "ThumbnailIds": ["/7C18VUCvZH5O0ibZogZP4TQJTiu.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.1, "NumRating": 420},
+ {"MovieId": "12160", "Title": "Wyatt Earp", "Casts": [], "PlotId": "12160",
+ "ThumbnailIds": ["/zSGDgcWHqmQxmX4mLJZgT5UgLjj.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 341}, {"MovieId": "327331", "Title": "The Dirt", "Casts": [], "PlotId": "327331",
+ "ThumbnailIds": ["/xGY5rr8441ib0lT9mtHZn7e8Aay.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.3, "NumRating": 265},
+ {"MovieId": "10328", "Title": "Cocoon", "Casts": [], "PlotId": "10328",
+ "ThumbnailIds": ["/foIhEPQoqDctfwsHmmYwbNz5A2g.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.5,
+ "NumRating": 564}, {"MovieId": "9696", "Title": "Ichi the Killer", "Casts": [], "PlotId": "9696",
+ "ThumbnailIds": ["/mkmwESVpq7KrcxdPMreTFqlNF0S.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.0, "NumRating": 379},
+ {"MovieId": "11565", "Title": "Big Momma's House 2", "Casts": [], "PlotId": "11565",
+ "ThumbnailIds": ["/tOl6hzfFHdNcL7SxopC2Vbs4mgK.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.6,
+ "NumRating": 777},
+ {"MovieId": "321612", "Title": "Beauty and the Beast", "Casts": [], "PlotId": "321612",
+ "ThumbnailIds": ["/tWqifoYuwLETmmasnGHO7xBjEtt.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.9,
+ "NumRating": 10886}, {"MovieId": "2163", "Title": "Breakdown", "Casts": [], "PlotId": "2163",
+ "ThumbnailIds": ["/uj5VmM4jrn3HHeuFfEH8XQizA2g.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.6, "NumRating": 348},
+ {"MovieId": "77805", "Title": "Lovelace", "Casts": [], "PlotId": "77805",
+ "ThumbnailIds": ["/6J4zI97usxdjhzobBkqFW1d9OQ5.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 360}, {"MovieId": "50725", "Title": "Take Me Home Tonight", "Casts": [], "PlotId": "50725",
+ "ThumbnailIds": ["/glRBoVKClP7qYQO0gQi5keCQ6ko.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.1, "NumRating": 326},
+ {"MovieId": "643", "Title": "Battleship Potemkin", "Casts": [], "PlotId": "643",
+ "ThumbnailIds": ["/tjnaRiHUsxBADaOwrQpnTnjHVwi.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.6,
+ "NumRating": 458}, {"MovieId": "916", "Title": "Bullitt", "Casts": [], "PlotId": "916",
+ "ThumbnailIds": ["/oyhnoFu2oKQfAIdu9YU8I8Ne0pX.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.2, "NumRating": 437},
+ {"MovieId": "336050", "Title": "Son of Saul", "Casts": [], "PlotId": "336050",
+ "ThumbnailIds": ["/AcjoM9JielY0Yi42GnICNBntpND.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.2,
+ "NumRating": 493},
+ {"MovieId": "335988", "Title": "Transformers: The Last Knight", "Casts": [], "PlotId": "335988",
+ "ThumbnailIds": ["/s5HQf2Gb3lIO2cRcFwNL9sn1o1o.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 3204}, {"MovieId": "16558", "Title": "Duplicity", "Casts": [], "PlotId": "16558",
+ "ThumbnailIds": ["/vpWQs3CjwG6Er3DgZlv4L3NFjXg.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.9, "NumRating": 346},
+ {"MovieId": "9594", "Title": "Double Impact", "Casts": [], "PlotId": "9594",
+ "ThumbnailIds": ["/sRxg7BI6y5dXa5SX0RZS3zlhxwc.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.5,
+ "NumRating": 371}, {"MovieId": "10712", "Title": "Far from Heaven", "Casts": [], "PlotId": "10712",
+ "ThumbnailIds": ["/7lzlqQmtv4z2CEiiBWU55blk1zo.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.1, "NumRating": 249},
+ {"MovieId": "11074", "Title": "Striking Distance", "Casts": [], "PlotId": "11074",
+ "ThumbnailIds": ["/1bLazrQWJQhO6u58vJZXJZ3ZFDh.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.7,
+ "NumRating": 363}, {"MovieId": "399055", "Title": "The Shape of Water", "Casts": [], "PlotId": "399055",
+ "ThumbnailIds": ["/k4FwHlMhuRR5BISY2Gm2QZHlH5Q.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.3, "NumRating": 7081},
+ {"MovieId": "13416", "Title": "Friday Night Lights", "Casts": [], "PlotId": "13416",
+ "ThumbnailIds": ["/8HIqpOgqShRc3TAleabnYispDl1.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.9,
+ "NumRating": 248}, {"MovieId": "300668", "Title": "Annihilation", "Casts": [], "PlotId": "300668",
+ "ThumbnailIds": ["/d3qcpfNwbAMCNqWDHzPQsUYiUgS.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.3, "NumRating": 4359},
+ {"MovieId": "119450", "Title": "Dawn of the Planet of the Apes", "Casts": [], "PlotId": "119450",
+ "ThumbnailIds": ["/2EUAUIu5lHFlkj5FRryohlH6CRO.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 6984}, {"MovieId": "11386", "Title": "The Crying Game", "Casts": [], "PlotId": "11386",
+ "ThumbnailIds": ["/9Qqk3svM0I9QuabOPDNrJZdv3XJ.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.0, "NumRating": 307},
+ {"MovieId": "10589", "Title": "After the Sunset", "Casts": [], "PlotId": "10589",
+ "ThumbnailIds": ["/seEavscJqc1TuHfSZMeXQwohkNf.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 340}, {"MovieId": "49026", "Title": "The Dark Knight Rises", "Casts": [], "PlotId": "49026",
+ "ThumbnailIds": ["/dEYnvnUfXrqvqeRSqvIEtmzhoA8.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.7, "NumRating": 13731},
+ {"MovieId": "1552", "Title": "Parenthood", "Casts": [], "PlotId": "1552",
+ "ThumbnailIds": ["/e51tNNQBJpJi9xkyuj0QFhyBcz7.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 267}, {"MovieId": "13944", "Title": "Passengers", "Casts": [], "PlotId": "13944",
+ "ThumbnailIds": ["/fGI0FFre8W454JqHgexV6zDWC9H.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.9, "NumRating": 344},
+ {"MovieId": "524309", "Title": "The Gift", "Casts": [], "PlotId": "524309",
+ "ThumbnailIds": ["/oiymQKFIqbCbWamrQ5EbOYdmjvn.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0,
+ "NumRating": 0},
+ {"MovieId": "9385", "Title": "The Twelve Tasks of Asterix", "Casts": [], "PlotId": "9385",
+ "ThumbnailIds": ["/7cZQZOYZFJcLjZnxWjO5PcUtmDZ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.5,
+ "NumRating": 501}, {"MovieId": "36419", "Title": "After.Life", "Casts": [], "PlotId": "36419",
+ "ThumbnailIds": ["/8mtT1Hqp63s3DiGxZckxQj0Mioy.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.6, "NumRating": 406},
+ {"MovieId": "775", "Title": "A Trip to the Moon", "Casts": [], "PlotId": "775",
+ "ThumbnailIds": ["/zztHYAfSecYuaDyIjQudjKaOLLY.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.0,
+ "NumRating": 672}, {"MovieId": "931", "Title": "Don't Look Now", "Casts": [], "PlotId": "931",
+ "ThumbnailIds": ["/qRUMQN3fa43ZuEldhNG7UYoURDG.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.1, "NumRating": 336},
+ {"MovieId": "77875", "Title": "Playing for Keeps", "Casts": [], "PlotId": "77875",
+ "ThumbnailIds": ["/eOBekVFBGK4TwETQSwfuk22B41o.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.6,
+ "NumRating": 410}, {"MovieId": "10876", "Title": "Quills", "Casts": [], "PlotId": "10876",
+ "ThumbnailIds": ["/4fz5LDFsapXDJVLsy2jh5VPZtR0.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.9, "NumRating": 238},
+ {"MovieId": "10675", "Title": "Frantic", "Casts": [], "PlotId": "10675",
+ "ThumbnailIds": ["/wOaBEIhiEA832ifEQ4CNxVHrh1c.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 414}, {"MovieId": "543343", "Title": "Guava Island", "Casts": [], "PlotId": "543343",
+ "ThumbnailIds": ["/noE2O4XaRSrNZ75MUShaQD0pFN0.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.5, "NumRating": 75},
+ {"MovieId": "9796", "Title": "Turistas", "Casts": [], "PlotId": "9796",
+ "ThumbnailIds": ["/lwj9HLVIfLaFYyF7ZIxBlntT9jM.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.3,
+ "NumRating": 354}, {"MovieId": "11817", "Title": "Bulletproof Monk", "Casts": [], "PlotId": "11817",
+ "ThumbnailIds": ["/hzk3kf5h54cCP3MYDptEjmLKVLF.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.3, "NumRating": 439},
+ {"MovieId": "11901", "Title": "High Plains Drifter", "Casts": [], "PlotId": "11901",
+ "ThumbnailIds": ["/557vwDdOIQ07Q1QvTwHVmxHJpXU.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.5,
+ "NumRating": 468}, {"MovieId": "49521", "Title": "Man of Steel", "Casts": [], "PlotId": "49521",
+ "ThumbnailIds": ["/xWlaTLnD8NJMTT9PGOD9z5re1SL.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.5, "NumRating": 9624},
+ {"MovieId": "228203", "Title": "McFarland, USA", "Casts": [], "PlotId": "228203",
+ "ThumbnailIds": ["/kV3Bk0PGwYhHLy1JppK3ZbVh7IB.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 353}, {"MovieId": "261", "Title": "Cat on a Hot Tin Roof", "Casts": [], "PlotId": "261",
+ "ThumbnailIds": ["/tkHug8LLo9dBgkVQqvmC4sib9HB.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.8, "NumRating": 306},
+ {"MovieId": "257445", "Title": "Goosebumps", "Casts": [], "PlotId": "257445",
+ "ThumbnailIds": ["/9yOnWCvpNr6RRhc4zhJdVqR7GKw.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 2087}, {"MovieId": "1730", "Title": "Inland Empire", "Casts": [], "PlotId": "1730",
+ "ThumbnailIds": ["/s5f0FbVAABEnJYKaApWORTxhiFC.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.1, "NumRating": 489},
+ {"MovieId": "10632", "Title": "The Hunted", "Casts": [], "PlotId": "10632",
+ "ThumbnailIds": ["/k8fV0lYH9DqGtB7M681DTa8hRuN.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 312}, {"MovieId": "11864", "Title": "Enemy Mine", "Casts": [], "PlotId": "11864",
+ "ThumbnailIds": ["/hKsZY8jHagbiTtvq8mwHV36a9ki.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.8, "NumRating": 397},
+ {"MovieId": "506", "Title": "Marnie", "Casts": [], "PlotId": "506",
+ "ThumbnailIds": ["/mwEuBWMJyebtJ1OP4W2jeRcVf3k.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 413},
+ {"MovieId": "9644", "Title": "National Lampoon's Loaded Weapon 1", "Casts": [], "PlotId": "9644",
+ "ThumbnailIds": ["/tIdlDgiVQ4kbgVXXIlP8LswubkN.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.8,
+ "NumRating": 284}, {"MovieId": "9093", "Title": "The Four Feathers", "Casts": [], "PlotId": "9093",
+ "ThumbnailIds": ["/1mr4V13SFK4En8f4ZdyBgnZmVar.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 259},
+ {"MovieId": "419430", "Title": "Get Out", "Casts": [], "PlotId": "419430",
+ "ThumbnailIds": ["/1SwAVYpuLj8KsHxllTF8Dt9dSSX.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.5,
+ "NumRating": 8505}, {"MovieId": "44945", "Title": "Trust", "Casts": [], "PlotId": "44945",
+ "ThumbnailIds": ["/oWEy6b2LxwcuZtWLmrrdBWp3hda.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.6, "NumRating": 546},
+ {"MovieId": "10303", "Title": "The Jewel of the Nile", "Casts": [], "PlotId": "10303",
+ "ThumbnailIds": ["/iQXxVWuXmaEPhieETkThDgGGKCb.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 492}, {"MovieId": "468", "Title": "My Own Private Idaho", "Casts": [], "PlotId": "468",
+ "ThumbnailIds": ["/xgWJPruxsL2TGVwhZDM8YiarbEY.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.1, "NumRating": 426},
+ {"MovieId": "16804", "Title": "Departures", "Casts": [], "PlotId": "16804",
+ "ThumbnailIds": ["/aiRKdQ3CqzMv88Zlk69utBBbseO.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.9,
+ "NumRating": 308}, {"MovieId": "1396", "Title": "Mirror", "Casts": [], "PlotId": "1396",
+ "ThumbnailIds": ["/9PknVc5uubhVLZ6ofvfJAprM9UZ.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.1, "NumRating": 300},
+ {"MovieId": "2830", "Title": "My Boss's Daughter", "Casts": [], "PlotId": "2830",
+ "ThumbnailIds": ["/du7joY11DJZeXXph7FwkJ3uo7om.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.6,
+ "NumRating": 350},
+ {"MovieId": "442062", "Title": "Goosebumps 2: Haunted Halloween", "Casts": [], "PlotId": "442062",
+ "ThumbnailIds": ["/kOrUF0EH2C3KHoI7tqANZMFZaTN.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.6,
+ "NumRating": 395}, {"MovieId": "1714", "Title": "Fahrenheit 451", "Casts": [], "PlotId": "1714",
+ "ThumbnailIds": ["/78NFjrD9onl9ciKSjsENUfnTHbT.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.2, "NumRating": 386},
+ {"MovieId": "10160", "Title": "A Nightmare on Elm Street: The Dream Child", "Casts": [],
+ "PlotId": "10160", "ThumbnailIds": ["/vnorsG2pKAVqIMYsDJbKoOr4CsX.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.4, "NumRating": 506},
+ {"MovieId": "17130", "Title": "Crossroads", "Casts": [], "PlotId": "17130",
+ "ThumbnailIds": ["/egIs4cWxn0iBErFKrQ7STckjj49.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.9,
+ "NumRating": 287},
+ {"MovieId": "102382", "Title": "The Amazing Spider-Man 2", "Casts": [], "PlotId": "102382",
+ "ThumbnailIds": ["/mUjWof8LHDgCZC9mFp0UYKBf1Dm.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 7085}, {"MovieId": "301", "Title": "Rio Bravo", "Casts": [], "PlotId": "301",
+ "ThumbnailIds": ["/gyEfGVDe5puz4wgIq6073fn8pHc.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.9, "NumRating": 471},
+ {"MovieId": "3085", "Title": "His Girl Friday", "Casts": [], "PlotId": "3085",
+ "ThumbnailIds": ["/fmQLvnDEL9wlE6FzB1S84yskdkT.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 308}, {"MovieId": "11184", "Title": "Kinsey", "Casts": [], "PlotId": "11184",
+ "ThumbnailIds": ["/aOmsDieEAsRDgDOYbY8tSUoTdnA.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 214},
+ {"MovieId": "10658", "Title": "Howard the Duck", "Casts": [], "PlotId": "10658",
+ "ThumbnailIds": ["/f2pj3SSj1GdFSrS5bUojT56umL6.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.1,
+ "NumRating": 416}, {"MovieId": "9626", "Title": "Red Sonja", "Casts": [], "PlotId": "9626",
+ "ThumbnailIds": ["/lvRlHJeMAYlUzSD8cebfIfNOhyl.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.1, "NumRating": 352},
+ {"MovieId": "140420", "Title": "Paperman", "Casts": [], "PlotId": "140420",
+ "ThumbnailIds": ["/3TpMBcAYH4cxCw5WoRacWodMTCG.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.1,
+ "NumRating": 1107}, {"MovieId": "205321", "Title": "Sharknado", "Casts": [], "PlotId": "205321",
+ "ThumbnailIds": ["/6nxewN7l5XGxDqCcGkLaXJ3ljdz.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 3.8, "NumRating": 863},
+ {"MovieId": "9945", "Title": "Vampires", "Casts": [], "PlotId": "9945",
+ "ThumbnailIds": ["/qHGawU64MeGvtU86s6V0MA7MqFV.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 456}, {"MovieId": "12142", "Title": "Alone in the Dark", "Casts": [], "PlotId": "12142",
+ "ThumbnailIds": ["/3HsprIjUEwYfnlEf7jumGm037Bk.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 3.1, "NumRating": 276},
+ {"MovieId": "38360", "Title": "The Cranes Are Flying", "Casts": [], "PlotId": "38360",
+ "ThumbnailIds": ["/xFE2hx2kGYcsQHttJobRajuyA6Q.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.1,
+ "NumRating": 110}, {"MovieId": "11137", "Title": "The Prince & Me", "Casts": [], "PlotId": "11137",
+ "ThumbnailIds": ["/nB75Ht9rppAVMj0lZWZQ51PPYUe.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.0, "NumRating": 516},
+ {"MovieId": "10923", "Title": "Agent Cody Banks", "Casts": [], "PlotId": "10923",
+ "ThumbnailIds": ["/fhK0mqqirPsckxkNisvi32A4lf6.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.2,
+ "NumRating": 560},
+ {"MovieId": "9728", "Title": "Friday the 13th Part III", "Casts": [], "PlotId": "9728",
+ "ThumbnailIds": ["/5wg2NZyIhcMbIBAahBODXHyJ54S.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.8,
+ "NumRating": 516}, {"MovieId": "10577", "Title": "Dracula 2000", "Casts": [], "PlotId": "10577",
+ "ThumbnailIds": ["/6YF56U91zP1mQvle83KA1A7CPop.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 4.7, "NumRating": 275},
+ {"MovieId": "1649", "Title": "Bill & Ted's Bogus Journey", "Casts": [], "PlotId": "1649",
+ "ThumbnailIds": ["/q8ssJWstfsWHmmFdigDk4r3l5gM.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 474}, {"MovieId": "9825", "Title": "Lake Placid", "Casts": [], "PlotId": "9825",
+ "ThumbnailIds": ["/6lE0EahK7xDOYWRH6On5uKPnwQZ.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.5, "NumRating": 429},
+ {"MovieId": "281338", "Title": "War for the Planet of the Apes", "Casts": [], "PlotId": "281338",
+ "ThumbnailIds": ["/ijQHiImv16vNSeZQsmih04kwn0C.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 5166}, {"MovieId": "512239", "Title": "The Corrupted", "Casts": [], "PlotId": "512239",
+ "ThumbnailIds": ["/tNGJw2R1l3XuLRi749GQaFua9yZ.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 0.0, "NumRating": 0},
+ {"MovieId": "10197", "Title": "Nine", "Casts": [], "PlotId": "10197",
+ "ThumbnailIds": ["/2PSmbXIUWgUVsXq2U1MoVj3f38g.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.5,
+ "NumRating": 308},
+ {"MovieId": "24438", "Title": "Did You Hear About the Morgans?", "Casts": [], "PlotId": "24438",
+ "ThumbnailIds": ["/yBrceyep5ZWaU9XuodM1X5c67K6.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.3,
+ "NumRating": 496}, {"MovieId": "8427", "Title": "I Spy", "Casts": [], "PlotId": "8427",
+ "ThumbnailIds": ["/6mtUJKyedvQwEKXfWzJt3vtWx1M.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.3, "NumRating": 475},
+ {"MovieId": "347969", "Title": "The Ridiculous 6", "Casts": [], "PlotId": "347969",
+ "ThumbnailIds": ["/k77xcsBtZMq9zIlTVQV7UBemzXD.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.9,
+ "NumRating": 661}, {"MovieId": "649", "Title": "Belle de Jour", "Casts": [], "PlotId": "649",
+ "ThumbnailIds": ["/rHvKWARrhNwSjbTMJrn5v4LtUJE.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.5, "NumRating": 310},
+ {"MovieId": "7548", "Title": "The Libertine", "Casts": [], "PlotId": "7548",
+ "ThumbnailIds": ["/a0gI62QrqJ7PjTsUhJlEsWOPHeU.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 236},
+ {"MovieId": "35552", "Title": "The Extraordinary Adventures of Ad\u00e8le Blanc-Sec", "Casts": [],
+ "PlotId": "35552", "ThumbnailIds": ["/og6Jv55bF0uzF1F1kpUW6Je7z0H.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.0, "NumRating": 655},
+ {"MovieId": "3525", "Title": "Working Girl", "Casts": [], "PlotId": "3525",
+ "ThumbnailIds": ["/bzPuHOl8DjfZDzwGDCIHonnHUT6.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 420}, {"MovieId": "13", "Title": "Forrest Gump", "Casts": [], "PlotId": "13",
+ "ThumbnailIds": ["/yE5d3BUhE8hCnkMUJOo1QDoOGNz.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.4, "NumRating": 14523},
+ {"MovieId": "100402", "Title": "Captain America: The Winter Soldier", "Casts": [], "PlotId": "100402",
+ "ThumbnailIds": ["/5TQ6YDmymBpnF005OyoB7ohZps9.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.7,
+ "NumRating": 10760}, {"MovieId": "936", "Title": "The Pink Panther", "Casts": [], "PlotId": "936",
+ "ThumbnailIds": ["/azIoCxiH9wIPCCGnqaDW8DJwCLl.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.1, "NumRating": 436},
+ {"MovieId": "56288", "Title": "Spy Kids: All the Time in the World", "Casts": [], "PlotId": "56288",
+ "ThumbnailIds": ["/eHldUGDNxb4ZPQSPDnGolyFDECa.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.7,
+ "NumRating": 475}, {"MovieId": "9549", "Title": "The Right Stuff", "Casts": [], "PlotId": "9549",
+ "ThumbnailIds": ["/df1gdyE9S3DxmTfAeN3pNdSm64J.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.5, "NumRating": 378},
+ {"MovieId": "11185", "Title": "See No Evil, Hear No Evil", "Casts": [], "PlotId": "11185",
+ "ThumbnailIds": ["/psB1Zb6sqnzHyCgtJrEJqphflVx.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 402}, {"MovieId": "10155", "Title": "U Turn", "Casts": [], "PlotId": "10155",
+ "ThumbnailIds": ["/mG4GerNC6ZVnsFTXcsTeKuj8OG.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 303},
+ {"MovieId": "1412", "Title": "sex, lies, and videotape", "Casts": [], "PlotId": "1412",
+ "ThumbnailIds": ["/kOXATYKziiKmoMxgKA10JOO13JZ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.9,
+ "NumRating": 328}, {"MovieId": "11559", "Title": "Tideland", "Casts": [], "PlotId": "11559",
+ "ThumbnailIds": ["/xR8vo6xGJbk8QljOlcjLNyLrwB5.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.5, "NumRating": 245},
+ {"MovieId": "1554", "Title": "Down by Law", "Casts": [], "PlotId": "1554",
+ "ThumbnailIds": ["/r4HxZPAhfg2akFaixpVIAM83GCY.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.5,
+ "NumRating": 280}, {"MovieId": "11416", "Title": "The Mission", "Casts": [], "PlotId": "11416",
+ "ThumbnailIds": ["/oTU13XXz4WHisDWKX3X6dFWEjC0.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.3, "NumRating": 574},
+ {"MovieId": "11253", "Title": "Hellboy II: The Golden Army", "Casts": [], "PlotId": "11253",
+ "ThumbnailIds": ["/fFcZqnWDeQsImDAAIyAimc3SGEl.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 2761}, {"MovieId": "12154", "Title": "3 Men and a Baby", "Casts": [], "PlotId": "12154",
+ "ThumbnailIds": ["/q4O2ravqDhUTAsshllGv7orgJOO.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.9, "NumRating": 447},
+ {"MovieId": "209403", "Title": "Bad Words", "Casts": [], "PlotId": "209403",
+ "ThumbnailIds": ["/cEnpV3XLvgTp3eVXhY2eyQKZk4r.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 444}, {"MovieId": "9443", "Title": "Chariots of Fire", "Casts": [], "PlotId": "9443",
+ "ThumbnailIds": ["/Ae5ABhyD30jY9rkciOVCG8nJDwO.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.0, "NumRating": 369},
+ {"MovieId": "206647", "Title": "Spectre", "Casts": [], "PlotId": "206647",
+ "ThumbnailIds": ["/hE24GYddaxB9MVZl1CaiI86M3kp.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 6648}, {"MovieId": "98548", "Title": "People Like Us", "Casts": [], "PlotId": "98548",
+ "ThumbnailIds": ["/lMAHB1r1vSUOZRKCYR143mm6VMk.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.8, "NumRating": 355},
+ {"MovieId": "44629", "Title": "Animal Kingdom", "Casts": [], "PlotId": "44629",
+ "ThumbnailIds": ["/zhj8YPQKuRev5N3KoHacsPnF4mB.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.8,
+ "NumRating": 395}, {"MovieId": "63493", "Title": "The Ledge", "Casts": [], "PlotId": "63493",
+ "ThumbnailIds": ["/7iHQhaF2LRGlHpZcjcdODDn6pj.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.1, "NumRating": 183},
+ {"MovieId": "1710", "Title": "Copycat", "Casts": [], "PlotId": "1710",
+ "ThumbnailIds": ["/80czeJGSoik22fhtUM9WzyjUU4r.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.5,
+ "NumRating": 398}, {"MovieId": "10398", "Title": "Double Jeopardy", "Casts": [], "PlotId": "10398",
+ "ThumbnailIds": ["/9Pl7lqUzU7lxQHYjcmTT6ZvbbDY.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.4, "NumRating": 503},
+ {"MovieId": "9607", "Title": "Super Mario Bros.", "Casts": [], "PlotId": "9607",
+ "ThumbnailIds": ["/bmv7fmcBFzjnJvirfAdZm75qERY.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.1,
+ "NumRating": 478}, {"MovieId": "3132", "Title": "Bad Company", "Casts": [], "PlotId": "3132",
+ "ThumbnailIds": ["/v4Jz1ALH2LdIrpr681WVIsYbRQL.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.4, "NumRating": 360},
+ {"MovieId": "9879", "Title": "Striptease", "Casts": [], "PlotId": "9879",
+ "ThumbnailIds": ["/4zMy6R7acotCmGoDk4sjzRtDwKn.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.7,
+ "NumRating": 351}, {"MovieId": "11929", "Title": "Dolores Claiborne", "Casts": [], "PlotId": "11929",
+ "ThumbnailIds": ["/ewmVWV0TP8LTlYZ4OzCcguwC9d1.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.2, "NumRating": 310},
+ {"MovieId": "9664", "Title": "Flyboys", "Casts": [], "PlotId": "9664",
+ "ThumbnailIds": ["/ap7UcuDPhyaY165YppEZJdbU1sA.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 414}, {"MovieId": "36819", "Title": "Time Bandits", "Casts": [], "PlotId": "36819",
+ "ThumbnailIds": ["/4VZtpwdhHQSa4LUkvujyGAHb1hG.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 414},
+ {"MovieId": "9566", "Title": "The Fan", "Casts": [], "PlotId": "9566",
+ "ThumbnailIds": ["/fdW8T9kkYlPyOGh0V5eodFr8SQq.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.9,
+ "NumRating": 344}, {"MovieId": "9587", "Title": "Little Women", "Casts": [], "PlotId": "9587",
+ "ThumbnailIds": ["/tD9YDE8pjtSvvKUpkmooE6YDSm8.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.2, "NumRating": 466},
+ {"MovieId": "19255", "Title": "Away We Go", "Casts": [], "PlotId": "19255",
+ "ThumbnailIds": ["/zHDoca5jdrJQ8rwgN9HsWk8HRaG.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.8,
+ "NumRating": 299}, {"MovieId": "14636", "Title": "The Condemned", "Casts": [], "PlotId": "14636",
+ "ThumbnailIds": ["/dYocAyHDOUg4y4pcetRWqHWH0fi.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.9, "NumRating": 335},
+ {"MovieId": "14013", "Title": "BASEketball", "Casts": [], "PlotId": "14013",
+ "ThumbnailIds": ["/pEg0tYYhSfsyjLIYpvD9lCWLhcH.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 249}, {"MovieId": "9542", "Title": "The Hitcher", "Casts": [], "PlotId": "9542",
+ "ThumbnailIds": ["/pdPKlwAOX66Zqktp1amFooovAjT.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.0, "NumRating": 337},
+ {"MovieId": "141043", "Title": "A Long Way Down", "Casts": [], "PlotId": "141043",
+ "ThumbnailIds": ["/vIa83hicQj1ZFDG2bWfZaoUoa2e.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 522}, {"MovieId": "10784", "Title": "Cabaret", "Casts": [], "PlotId": "10784",
+ "ThumbnailIds": ["/1RmUfX3LcS897GhUWrWp5nRADo4.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.5, "NumRating": 389},
+ {"MovieId": "113833", "Title": "The Normal Heart", "Casts": [], "PlotId": "113833",
+ "ThumbnailIds": ["/fIf4nLpWHK8BsbH76fPgMbLSjuU.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.0,
+ "NumRating": 557}, {"MovieId": "11515", "Title": "Goya's Ghosts", "Casts": [], "PlotId": "11515",
+ "ThumbnailIds": ["/x04CKIV43Y9K5iTD98fB25lB0F2.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 214},
+ {"MovieId": "71864", "Title": "The Odd Life of Timothy Green", "Casts": [], "PlotId": "71864",
+ "ThumbnailIds": ["/nwWtTWWOJIDU1mM3cBYvSURB03B.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 599}, {"MovieId": "58232", "Title": "Chalet Girl", "Casts": [], "PlotId": "58232",
+ "ThumbnailIds": ["/64AQrBEKJVSIRaocOCtkMqExToz.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.3, "NumRating": 365},
+ {"MovieId": "10588", "Title": "The Cat in the Hat", "Casts": [], "PlotId": "10588",
+ "ThumbnailIds": ["/cyfCsdxGYIRlMd2z3dncOZWrgvk.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.1,
+ "NumRating": 715}, {"MovieId": "12155", "Title": "Alice in Wonderland", "Casts": [], "PlotId": "12155",
+ "ThumbnailIds": ["/pvEE5EN5N1yjmHmldfL4aJWm56l.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 8557},
+ {"MovieId": "4599", "Title": "Raising Helen", "Casts": [], "PlotId": "4599",
+ "ThumbnailIds": ["/oROgudstoH3i4KyI5ZJbBVW9mKK.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 371}, {"MovieId": "28597", "Title": "Problem Child 2", "Casts": [], "PlotId": "28597",
+ "ThumbnailIds": ["/npr7j2HuRgvsKrXLIxIiXevTH8A.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.3, "NumRating": 218},
+ {"MovieId": "2362", "Title": "Westworld", "Casts": [], "PlotId": "2362",
+ "ThumbnailIds": ["/cOJsaT8jEmG9s1MziVIPpHBRpQ7.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 583}, {"MovieId": "557", "Title": "Spider-Man", "Casts": [], "PlotId": "557",
+ "ThumbnailIds": ["/A9BYH1DSetvC7bjbHWCaL17Qbp5.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.0, "NumRating": 9635},
+ {"MovieId": "401469", "Title": "Widows", "Casts": [], "PlotId": "401469",
+ "ThumbnailIds": ["/d31SGJSaX29ba5ZUbZcesGoDE7I.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.5,
+ "NumRating": 878}, {"MovieId": "11634", "Title": "Show Me Love", "Casts": [], "PlotId": "11634",
+ "ThumbnailIds": ["/gkM27kmi9woapo1oQ2lho59u5Hn.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.2, "NumRating": 214},
+ {"MovieId": "10403", "Title": "The Player", "Casts": [], "PlotId": "10403",
+ "ThumbnailIds": ["/eoQ9H9CEySIYSPElKn2VRVY0MEa.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.2,
+ "NumRating": 301}, {"MovieId": "126889", "Title": "Alien: Covenant", "Casts": [], "PlotId": "126889",
+ "ThumbnailIds": ["/zecMELPbU5YMQpC81Z8ImaaXuf9.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.9, "NumRating": 4894},
+ {"MovieId": "21734", "Title": "Shadow of a Doubt", "Casts": [], "PlotId": "21734",
+ "ThumbnailIds": ["/sqHoIsWkGdPpX6zdPwX6HOMOWjj.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.7,
+ "NumRating": 379}, {"MovieId": "184314", "Title": "Young & Beautiful", "Casts": [], "PlotId": "184314",
+ "ThumbnailIds": ["/fqsab9s1yv60bDpak8p7b7R9Dn9.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.3, "NumRating": 581},
+ {"MovieId": "9597", "Title": "Vidocq", "Casts": [], "PlotId": "9597",
+ "ThumbnailIds": ["/7VQgyNCJV1TGpT8yTVDXaADQ5F2.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.7,
+ "NumRating": 236}, {"MovieId": "10468", "Title": "28 Days", "Casts": [], "PlotId": "10468",
+ "ThumbnailIds": ["/qTrrElHcONoix4hb4IpLiqEnjlb.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.0, "NumRating": 401},
+ {"MovieId": "76341", "Title": "Mad Max: Fury Road", "Casts": [], "PlotId": "76341",
+ "ThumbnailIds": ["/kqjL17yufvn9OVLyXYpvtyrFfak.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 14217}, {"MovieId": "543540", "Title": "The Perfect Date", "Casts": [], "PlotId": "543540",
+ "ThumbnailIds": ["/pi7J3iH3Z1NO9q2E13ChY7imyKb.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.4, "NumRating": 1114},
+ {"MovieId": "51300", "Title": "The Barber of Siberia", "Casts": [], "PlotId": "51300",
+ "ThumbnailIds": ["/xganmHk2g5PnzENlkGvg7brbbbM.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 54}, {"MovieId": "10808", "Title": "Dr. Dolittle 2", "Casts": [], "PlotId": "10808",
+ "ThumbnailIds": ["/aecUc1LobMV56Doj99ZvJR2dXgG.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.1, "NumRating": 862},
+ {"MovieId": "62046", "Title": "Flypaper", "Casts": [], "PlotId": "62046",
+ "ThumbnailIds": ["/czpYP8p0HBXNL3cIsGFMd0a9rSu.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.3,
+ "NumRating": 395}, {"MovieId": "106", "Title": "Predator", "Casts": [], "PlotId": "106",
+ "ThumbnailIds": ["/gUpto7r2XwoM5eW7MUvd8hl1etB.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.4, "NumRating": 3658},
+ {"MovieId": "204", "Title": "The Wages of Fear", "Casts": [], "PlotId": "204",
+ "ThumbnailIds": ["/3IGuAr1xsErR4XqmHZcfyQ4f8KY.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.1,
+ "NumRating": 314},
+ {"MovieId": "106646", "Title": "The Wolf of Wall Street", "Casts": [], "PlotId": "106646",
+ "ThumbnailIds": ["/vK1o5rZGqxyovfIhZyMELhk03wO.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.9,
+ "NumRating": 12171},
+ {"MovieId": "13067", "Title": "In the Land of Women", "Casts": [], "PlotId": "13067",
+ "ThumbnailIds": ["/vyt7OcWLFc0OsnNSh8XY7Z2bCur.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.8,
+ "NumRating": 265}, {"MovieId": "209263", "Title": "Enough Said", "Casts": [], "PlotId": "209263",
+ "ThumbnailIds": ["/gPJ7KyzdHVpCaxdiq22sPEmNJZV.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 461},
+ {"MovieId": "484247", "Title": "A Simple Favor", "Casts": [], "PlotId": "484247",
+ "ThumbnailIds": ["/5EJWZQ8dh99hfgXP9zAD5Ak5Hrn.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 1675},
+ {"MovieId": "567604", "Title": "Once Upon a Deadpool", "Casts": [], "PlotId": "567604",
+ "ThumbnailIds": ["/5Ka49BWWyKMXr93YMbH5wLN7aAM.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 213}, {"MovieId": "13576", "Title": "This Is It", "Casts": [], "PlotId": "13576",
+ "ThumbnailIds": ["/4ZgT8FRJxxpG0mgOIPeCgjzAsBR.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.8, "NumRating": 420},
+ {"MovieId": "34480", "Title": "The Descent: Part 2", "Casts": [], "PlotId": "34480",
+ "ThumbnailIds": ["/4rZtrCSvDjK43kO2rK2z9FaytlW.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 468}, {"MovieId": "44147", "Title": "Wild Target", "Casts": [], "PlotId": "44147",
+ "ThumbnailIds": ["/x1O8MOIY41fpKbCUt1I4sKeLwwr.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.4, "NumRating": 352},
+ {"MovieId": "1023", "Title": "Adam's Apples", "Casts": [], "PlotId": "1023",
+ "ThumbnailIds": ["/ysLgwV21cnoI9nkWIvdZWqJuwjE.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.5,
+ "NumRating": 296}, {"MovieId": "150540", "Title": "Inside Out", "Casts": [], "PlotId": "150540",
+ "ThumbnailIds": ["/aAmfIX3TT40zUHGcCKrlOZRKC7u.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.0, "NumRating": 12454},
+ {"MovieId": "234200", "Title": "Pride", "Casts": [], "PlotId": "234200",
+ "ThumbnailIds": ["/fk81iMvZTYo7MaYWGirPc0uzA55.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.9,
+ "NumRating": 725}, {"MovieId": "360920", "Title": "The Grinch", "Casts": [], "PlotId": "360920",
+ "ThumbnailIds": ["/gpkHvkCtZOeCQ2DelnJ2LB1WjZ5.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.4, "NumRating": 1005},
+ {"MovieId": "2665", "Title": "Airplane II: The Sequel", "Casts": [], "PlotId": "2665",
+ "ThumbnailIds": ["/pjI6j5sVTxJXuxnr2JM2FgvyFXS.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 459},
+ {"MovieId": "318121", "Title": "The Fundamentals of Caring", "Casts": [], "PlotId": "318121",
+ "ThumbnailIds": ["/k1JZUr1wwpEdTwSVQdZWheRAIui.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 1181}, {"MovieId": "24056", "Title": "The Tournament", "Casts": [], "PlotId": "24056",
+ "ThumbnailIds": ["/ldqexoGgxnuV1hpX64MR6WnyEVG.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.8, "NumRating": 282},
+ {"MovieId": "11562", "Title": "Crimes and Misdemeanors", "Casts": [], "PlotId": "11562",
+ "ThumbnailIds": ["/8I7Dzaah4FD6PTUjwIFm4H6Md0U.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.6,
+ "NumRating": 375}, {"MovieId": "394741", "Title": "Stan & Ollie", "Casts": [], "PlotId": "394741",
+ "ThumbnailIds": ["/8qDBDXA8Od8gc4IQMnoXUKyj8Pf.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.1, "NumRating": 207},
+ {"MovieId": "73937", "Title": "The Big Year", "Casts": [], "PlotId": "73937",
+ "ThumbnailIds": ["/kBnR6jbFRnbSQTqLEcDpKd7FxK5.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.8,
+ "NumRating": 383}, {"MovieId": "10207", "Title": "Message in a Bottle", "Casts": [], "PlotId": "10207",
+ "ThumbnailIds": ["/majQar87QNbrC47qYMcmK1oCohZ.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.1, "NumRating": 373},
+ {"MovieId": "10757", "Title": "Kabhi Khushi Kabhie Gham", "Casts": [], "PlotId": "10757",
+ "ThumbnailIds": ["/1WfrkYkYL52JQCMtoeMbWdjRmv6.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.6,
+ "NumRating": 194}, {"MovieId": "1833", "Title": "Rent", "Casts": [], "PlotId": "1833",
+ "ThumbnailIds": ["/c3GY40OxnPQZZPJk6WOShw63HGn.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.9, "NumRating": 284},
+ {"MovieId": "2742", "Title": "Naked Lunch", "Casts": [], "PlotId": "2742",
+ "ThumbnailIds": ["/tMninSG4OAAhx9SNjdnMJM1R3Wn.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.1,
+ "NumRating": 404}, {"MovieId": "10762", "Title": "Without a Paddle", "Casts": [], "PlotId": "10762",
+ "ThumbnailIds": ["/q1jdD13xunSf22Ts4pAhVbVJ6IH.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.4, "NumRating": 324},
+ {"MovieId": "250066", "Title": "American Heist", "Casts": [], "PlotId": "250066",
+ "ThumbnailIds": ["/eqqf7DvhOBaEHiqLMkMMp0e8wzp.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.6,
+ "NumRating": 210}, {"MovieId": "245706", "Title": "True Story", "Casts": [], "PlotId": "245706",
+ "ThumbnailIds": ["/hBG9DzRbzId4uC4aUv3XTDZCn4i.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.2, "NumRating": 881},
+ {"MovieId": "6471", "Title": "The Jerk", "Casts": [], "PlotId": "6471",
+ "ThumbnailIds": ["/wb6mB6R9vscPhyAegbbNDFZUAIs.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 410}, {"MovieId": "2062", "Title": "Ratatouille", "Casts": [], "PlotId": "2062",
+ "ThumbnailIds": ["/xVxxSYHAfrEbllyWFQG5df5nwH4.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.6, "NumRating": 8847},
+ {"MovieId": "11030", "Title": "Zelig", "Casts": [], "PlotId": "11030",
+ "ThumbnailIds": ["/5dgjgBMWHcOrXL7EbGnU73WpXNB.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.5,
+ "NumRating": 380}, {"MovieId": "1613", "Title": "The 51st State", "Casts": [], "PlotId": "1613",
+ "ThumbnailIds": ["/hLtjOggvtlpBQ9tRNp5OwMw5mIk.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.2, "NumRating": 279},
+ {"MovieId": "10495", "Title": "The Karate Kid, Part III", "Casts": [], "PlotId": "10495",
+ "ThumbnailIds": ["/2Z0EJl11kOSPMMvHqZ4r5Csh7Ph.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.7,
+ "NumRating": 609},
+ {"MovieId": "50837", "Title": "Martha Marcy May Marlene", "Casts": [], "PlotId": "50837",
+ "ThumbnailIds": ["/f68uBuxkEfesHmw5eJxFOnNalTY.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 409}, {"MovieId": "2039", "Title": "Moonstruck", "Casts": [], "PlotId": "2039",
+ "ThumbnailIds": ["/2OIxyH2zMPYBzaECdIlX81Qc398.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.8, "NumRating": 347},
+ {"MovieId": "11453", "Title": "Deuce Bigalow: European Gigolo", "Casts": [], "PlotId": "11453",
+ "ThumbnailIds": ["/1P8fWex0NzgtPE4cgIUDHrVSARM.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.7,
+ "NumRating": 406}, {"MovieId": "85", "Title": "Raiders of the Lost Ark", "Casts": [], "PlotId": "85",
+ "ThumbnailIds": ["/44sKJOGP3fTm4QXBcIuqu0RkdP7.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.9, "NumRating": 6505},
+ {"MovieId": "332", "Title": "Inspector Gadget", "Casts": [], "PlotId": "332",
+ "ThumbnailIds": ["/kvlKBGtyyNVyGJB72aD1vwPk2d4.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.4,
+ "NumRating": 602}, {"MovieId": "4133", "Title": "Blow", "Casts": [], "PlotId": "4133",
+ "ThumbnailIds": ["/yCLLbZzAa7jreGus7pvjZmL0bj7.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.4, "NumRating": 2314},
+ {"MovieId": "49950", "Title": "The Roommate", "Casts": [], "PlotId": "49950",
+ "ThumbnailIds": ["/yvKZBncLbuNd3HX7dcqcepq83qy.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.3,
+ "NumRating": 401}, {"MovieId": "11006", "Title": "Smokey and the Bandit", "Casts": [], "PlotId": "11006",
+ "ThumbnailIds": ["/5uTIEUSBVzmEZ8TnFEZbCCOVHPj.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.8, "NumRating": 306},
+ {"MovieId": "10060", "Title": "Get Rich or Die Tryin'", "Casts": [], "PlotId": "10060",
+ "ThumbnailIds": ["/wKeSnhQfdwrycHorc9OPQ5KxVxJ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 351}, {"MovieId": "11324", "Title": "Shutter Island", "Casts": [], "PlotId": "11324",
+ "ThumbnailIds": ["/aZqKsvpJDFy2UzUMsdskNFbfkOd.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.1, "NumRating": 12391},
+ {"MovieId": "576071", "Title": "Unplanned", "Casts": [], "PlotId": "576071",
+ "ThumbnailIds": ["/hQvf3RHgmp4XXXl2y6zhMe4G4kg.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.8,
+ "NumRating": 22},
+ {"MovieId": "11658", "Title": "Tae Guk Gi: The Brotherhood of War", "Casts": [], "PlotId": "11658",
+ "ThumbnailIds": ["/1SEDI2qeNZgBK5XiJfKwWTmhZqC.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.6,
+ "NumRating": 217},
+ {"MovieId": "11850", "Title": "Invasion of the Body Snatchers", "Casts": [], "PlotId": "11850",
+ "ThumbnailIds": ["/skS02wdeH2C0nrbCQP3qKwJdZtZ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 384},
+ {"MovieId": "98566", "Title": "Teenage Mutant Ninja Turtles", "Casts": [], "PlotId": "98566",
+ "ThumbnailIds": ["/oDL2ryJ0sV2bmjgshVgJb3qzvwp.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.8,
+ "NumRating": 4192}, {"MovieId": "10488", "Title": "Nim's Island", "Casts": [], "PlotId": "10488",
+ "ThumbnailIds": ["/wNSQsaz1btAHSZ6vNJpjbVmQWQG.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.8, "NumRating": 630},
+ {"MovieId": "2000", "Title": "Aguirre: The Wrath of God", "Casts": [], "PlotId": "2000",
+ "ThumbnailIds": ["/uHP3AtAnudp5w8d3jx2el1AtV6a.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.6,
+ "NumRating": 400}, {"MovieId": "11566", "Title": "Dave", "Casts": [], "PlotId": "11566",
+ "ThumbnailIds": ["/nb2tSTxjhuiJqn9R6BEcwopq0dW.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.5, "NumRating": 329},
+ {"MovieId": "10907", "Title": "The Adventures of Robin Hood", "Casts": [], "PlotId": "10907",
+ "ThumbnailIds": ["/lEx5IKjqwYS7PcGnIXivYaDMzQr.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.6,
+ "NumRating": 286}, {"MovieId": "10412", "Title": "Romper Stomper", "Casts": [], "PlotId": "10412",
+ "ThumbnailIds": ["/wW9J9InM3gdZS6vcoJVQJo9pIVj.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 224},
+ {"MovieId": "30197", "Title": "The Producers", "Casts": [], "PlotId": "30197",
+ "ThumbnailIds": ["/xgKikS0QQcWS1CvhjbemeoBFd32.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 293}, {"MovieId": "402900", "Title": "Ocean's Eight", "Casts": [], "PlotId": "402900",
+ "ThumbnailIds": ["/MvYpKlpFukTivnlBhizGbkAe3v.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.9, "NumRating": 3548},
+ {"MovieId": "14292", "Title": "Miracle", "Casts": [], "PlotId": "14292",
+ "ThumbnailIds": ["/u2OCExn2VMgEQnVeoGaHl9bSrtQ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.1,
+ "NumRating": 264}, {"MovieId": "5820", "Title": "The Sentinel", "Casts": [], "PlotId": "5820",
+ "ThumbnailIds": ["/wUE2EsKkktVz8IiTzrpO6GbI6gh.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.9, "NumRating": 355},
+ {"MovieId": "16899", "Title": "Easy Virtue", "Casts": [], "PlotId": "16899",
+ "ThumbnailIds": ["/1VO09so985ZSbEj0JnuebHMqTip.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.3,
+ "NumRating": 191},
+ {"MovieId": "6878", "Title": "Homeward Bound: The Incredible Journey", "Casts": [], "PlotId": "6878",
+ "ThumbnailIds": ["/el6dJEpK97OJRQiQhuiSGk2jkV5.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 412}, {"MovieId": "472451", "Title": "Boy Erased", "Casts": [], "PlotId": "472451",
+ "ThumbnailIds": ["/oZbhTdi0ZQY7iiSQ0L7h3ya6NDF.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.2, "NumRating": 374},
+ {"MovieId": "8764", "Title": "Top Secret!", "Casts": [], "PlotId": "8764",
+ "ThumbnailIds": ["/2ArC4IAAGXf3hTVJCzQfE0G5Vbf.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 428}, {"MovieId": "13595", "Title": "Airheads", "Casts": [], "PlotId": "13595",
+ "ThumbnailIds": ["/jx06ghWxAC0BdrxUa03rti81RcY.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.8, "NumRating": 341},
+ {"MovieId": "83", "Title": "Open Water", "Casts": [], "PlotId": "83",
+ "ThumbnailIds": ["/hua2eluUhiLvKqwHFPV2aTiY8pp.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.4,
+ "NumRating": 558}, {"MovieId": "2313", "Title": "Prime", "Casts": [], "PlotId": "2313",
+ "ThumbnailIds": ["/dQxtI83slU5fAq6WZjEmIDAtYvM.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.9, "NumRating": 456},
+ {"MovieId": "11787", "Title": "Harvey", "Casts": [], "PlotId": "11787",
+ "ThumbnailIds": ["/dgd82hYmpiXDM1G867HqNaWe8wj.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.8,
+ "NumRating": 274}, {"MovieId": "636", "Title": "THX 1138", "Casts": [], "PlotId": "636",
+ "ThumbnailIds": ["/8cie5mojY6MlIrYMs9EtNSyterv.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 414},
+ {"MovieId": "13193", "Title": "Saved!", "Casts": [], "PlotId": "13193",
+ "ThumbnailIds": ["/sh61XwFfemE6MrDzAVNpJb6ZRgR.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 223}, {"MovieId": "12093", "Title": "Lilya 4-ever", "Casts": [], "PlotId": "12093",
+ "ThumbnailIds": ["/1uqtBLEXFO6qxofV9Hm1SigE3WH.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.8, "NumRating": 239},
+ {"MovieId": "604", "Title": "The Matrix Reloaded", "Casts": [], "PlotId": "604",
+ "ThumbnailIds": ["/ezIurBz2fdUc68d98Fp9dRf5ihv.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.9,
+ "NumRating": 5597}, {"MovieId": "44716", "Title": "In a Better World", "Casts": [], "PlotId": "44716",
+ "ThumbnailIds": ["/qxCTUrnC5ZdsKQepBMPt09ihse.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.3, "NumRating": 185},
+ {"MovieId": "588001", "Title": "Despite Everything", "Casts": [], "PlotId": "588001",
+ "ThumbnailIds": ["/1GH4KCS8IgWcDt5toXYFYX5AmX4.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.5,
+ "NumRating": 60},
+ {"MovieId": "57158", "Title": "The Hobbit: The Desolation of Smaug", "Casts": [], "PlotId": "57158",
+ "ThumbnailIds": ["/gQCiuxGsfiXH1su6lp9n0nd0UeH.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.6,
+ "NumRating": 7602}, {"MovieId": "500904", "Title": "A Vigilante", "Casts": [], "PlotId": "500904",
+ "ThumbnailIds": ["/avoKZfgBzyBGJsrAr2WQOwZK978.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 4.7, "NumRating": 53},
+ {"MovieId": "595680", "Title": "Il grande spirito", "Casts": [], "PlotId": "595680",
+ "ThumbnailIds": ["/hFuxBhwfj75c0fy1byTheD1aAKC.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0,
+ "NumRating": 0}, {"MovieId": "260", "Title": "The 39 Steps", "Casts": [], "PlotId": "260",
+ "ThumbnailIds": ["/9v283GWj9a0AC8wwC4zriNqY1lZ.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.4, "NumRating": 376},
+ {"MovieId": "17295", "Title": "The Battle of Algiers", "Casts": [], "PlotId": "17295",
+ "ThumbnailIds": ["/zsNc43QfSqeMBW186o9Fozfmkst.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.0,
+ "NumRating": 279},
+ {"MovieId": "234", "Title": "The Cabinet of Dr. Caligari", "Casts": [], "PlotId": "234",
+ "ThumbnailIds": ["/myK9DeIsXWGKgUTZyGXg2IfFk0W.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.9,
+ "NumRating": 536}, {"MovieId": "12690", "Title": "Appaloosa", "Casts": [], "PlotId": "12690",
+ "ThumbnailIds": ["/ar26XwTJz6BPFRricdNpMctyjB0.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.5, "NumRating": 475},
+ {"MovieId": "1694", "Title": "Re-Animator", "Casts": [], "PlotId": "1694",
+ "ThumbnailIds": ["/hJnP2O0uTuh8HsR094WLTi3sQwc.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.1,
+ "NumRating": 472},
+ {"MovieId": "780", "Title": "The Passion of Joan of Arc", "Casts": [], "PlotId": "780",
+ "ThumbnailIds": ["/5HL0dEJfd7PF0eRiKz8BiNfe8Tf.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.1,
+ "NumRating": 319}, {"MovieId": "19833", "Title": "In the Loop", "Casts": [], "PlotId": "19833",
+ "ThumbnailIds": ["/jL6txnziFSeEifQkqnPBtaPaiXU.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.2, "NumRating": 293},
+ {"MovieId": "405774", "Title": "Bird Box", "Casts": [], "PlotId": "405774",
+ "ThumbnailIds": ["/rGfGfgL2pEPCfhIvqHXieXFn7gp.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 4562}, {"MovieId": "452832", "Title": "Serenity", "Casts": [], "PlotId": "452832",
+ "ThumbnailIds": ["/hgWAcic93phg4DOuQ8NrsgQWiqu.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.1, "NumRating": 282},
+ {"MovieId": "10136", "Title": "The Golden Child", "Casts": [], "PlotId": "10136",
+ "ThumbnailIds": ["/nVJIyS2gh0hBvVEr8s9SrpSBTxL.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.7,
+ "NumRating": 495}, {"MovieId": "531", "Title": "The Wrong Trousers", "Casts": [], "PlotId": "531",
+ "ThumbnailIds": ["/O3fFWazkIL1QrmH5t9numsUgmR.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.6, "NumRating": 457},
+ {"MovieId": "581585", "Title": "A Regular Woman", "Casts": [], "PlotId": "581585",
+ "ThumbnailIds": ["/eXArCCQj3W4b55SohTo5fBHiu7G.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0,
+ "NumRating": 0}, {"MovieId": "41154", "Title": "Men in Black 3", "Casts": [], "PlotId": "41154",
+ "ThumbnailIds": ["/l9hrvXyGq19f6jPRZhSVRibTMwW.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.4, "NumRating": 6297},
+ {"MovieId": "956", "Title": "Mission: Impossible III", "Casts": [], "PlotId": "956",
+ "ThumbnailIds": ["/qjy8ABAbWooV4jLG6UjzDHlv4RB.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 3513}, {"MovieId": "13532", "Title": "Fanboys", "Casts": [], "PlotId": "13532",
+ "ThumbnailIds": ["/ywI92p2x7D96nuEayGVOMll35SF.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.2, "NumRating": 436},
+ {"MovieId": "351044", "Title": "Welcome to Marwen", "Casts": [], "PlotId": "351044",
+ "ThumbnailIds": ["/o45VIAUYDcVCGuzd43l8Sr5Dfti.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.5,
+ "NumRating": 241},
+ {"MovieId": "9876", "Title": "Stop! Or My Mom Will Shoot", "Casts": [], "PlotId": "9876",
+ "ThumbnailIds": ["/sQXdlCaX6demEdOgI1FbV9fX8aO.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.9,
+ "NumRating": 410},
+ {"MovieId": "595188", "Title": "Pariban : Idola Dari Tanah Jawa", "Casts": [], "PlotId": "595188",
+ "ThumbnailIds": ["/yxiDlS5tKpuPHHl4eOLgJCdTqU6.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0,
+ "NumRating": 0}, {"MovieId": "10985", "Title": "The New Guy", "Casts": [], "PlotId": "10985",
+ "ThumbnailIds": ["/uqUVToyQmcqBqm7USWEQIYDNERd.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.6, "NumRating": 251},
+ {"MovieId": "11469", "Title": "Black Knight", "Casts": [], "PlotId": "11469",
+ "ThumbnailIds": ["/xJnGEOnYUelD6wLgpQfNg5tq8jV.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.2,
+ "NumRating": 533}, {"MovieId": "11377", "Title": "House on Haunted Hill", "Casts": [], "PlotId": "11377",
+ "ThumbnailIds": ["/yedchBbI23FgDjWP1tvahOZgiks.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.5, "NumRating": 373},
+ {"MovieId": "13291", "Title": "Traitor", "Casts": [], "PlotId": "13291",
+ "ThumbnailIds": ["/euLk9RxsPHfpyWUHzTDNY6Dksch.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 321},
+ {"MovieId": "10242", "Title": "What Ever Happened to Baby Jane?", "Casts": [], "PlotId": "10242",
+ "ThumbnailIds": ["/t2hPlHc2pFweBqQgrsNfSLNIv1j.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.1,
+ "NumRating": 418},
+ {"MovieId": "887", "Title": "The Best Years of Our Lives", "Casts": [], "PlotId": "887",
+ "ThumbnailIds": ["/fxjWVRlmD0EKn8ZgFKREqpkTiRH.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.8,
+ "NumRating": 236}, {"MovieId": "21755", "Title": "The Brothers Bloom", "Casts": [], "PlotId": "21755",
+ "ThumbnailIds": ["/xIz8iwzyjgWGzcIwHKpqhBs77ML.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 318},
+ {"MovieId": "4105", "Title": "Black Rain", "Casts": [], "PlotId": "4105",
+ "ThumbnailIds": ["/dmMIMuByEbzE0x73gjc9YcDjjlx.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 434}, {"MovieId": "5961", "Title": "Fanny & Alexander", "Casts": [], "PlotId": "5961",
+ "ThumbnailIds": ["/zZVkPy2PJuWWZbYGXG38a1nZp7l.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.8, "NumRating": 275},
+ {"MovieId": "10494", "Title": "Perfect Blue", "Casts": [], "PlotId": "10494",
+ "ThumbnailIds": ["/sxBzVuwqIABKIbdij7lOrRvDb15.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.1,
+ "NumRating": 638}, {"MovieId": "353576", "Title": "The Con Is On", "Casts": [], "PlotId": "353576",
+ "ThumbnailIds": ["/d4ZzzFOokK356jf6L6vUp3RKNvM.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 4.0, "NumRating": 54},
+ {"MovieId": "181533", "Title": "Night at the Museum: Secret of the Tomb", "Casts": [], "PlotId": "181533",
+ "ThumbnailIds": ["/tWwASv4CU1Au1IukacdSUewDCV3.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 3467}, {"MovieId": "854", "Title": "The Mask", "Casts": [], "PlotId": "854",
+ "ThumbnailIds": ["/v8x8p441l1Bep8p82pAG6rduBoK.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.8, "NumRating": 5123},
+ {"MovieId": "449924", "Title": "Ip Man 4", "Casts": [], "PlotId": "449924",
+ "ThumbnailIds": ["/mAWBfTDAmfpvQGMVFuzuVl49N1P.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0,
+ "NumRating": 0}, {"MovieId": "607", "Title": "Men in Black", "Casts": [], "PlotId": "607",
+ "ThumbnailIds": ["/f24UVKq3UiQWLqGWdqjwkzgB8j8.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.0, "NumRating": 7726},
+ {"MovieId": "499533", "Title": "Float Like A Butterfly", "Casts": [], "PlotId": "499533",
+ "ThumbnailIds": ["/qOLSeKboDzZ1lPfuMTFWOj9Xl0z.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0,
+ "NumRating": 0}, {"MovieId": "6623", "Title": "The Peacemaker", "Casts": [], "PlotId": "6623",
+ "ThumbnailIds": ["/hc3p6pvIrfO4AmrVLb6qTviOwW2.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.8, "NumRating": 446},
+ {"MovieId": "10681", "Title": "WALL\u00b7E", "Casts": [], "PlotId": "10681",
+ "ThumbnailIds": ["/9cJETuLMc6R0bTWRA5i7ctY9bxk.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.0,
+ "NumRating": 10623},
+ {"MovieId": "16869", "Title": "Inglourious Basterds", "Casts": [], "PlotId": "16869",
+ "ThumbnailIds": ["/ai0LXkzVM3hMjDhvFdKMUemoBe.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.1,
+ "NumRating": 11739}, {"MovieId": "49012", "Title": "Arthur", "Casts": [], "PlotId": "49012",
+ "ThumbnailIds": ["/28HW1Kc4OwQH50M0XmcEuIYrCWk.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.4, "NumRating": 395},
+ {"MovieId": "9612", "Title": "Coneheads", "Casts": [], "PlotId": "9612",
+ "ThumbnailIds": ["/9vuFI0xV1yZfsWr23evJlkRDL8j.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.0,
+ "NumRating": 394}, {"MovieId": "218", "Title": "The Terminator", "Casts": [], "PlotId": "218",
+ "ThumbnailIds": ["/q8ffBuxQlYOHrvPniLgCbmKK4Lv.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.5, "NumRating": 6691},
+ {"MovieId": "4244", "Title": "The Kid", "Casts": [], "PlotId": "4244",
+ "ThumbnailIds": ["/bTsn3l3QLmtKGPnOkKWpLObOgu7.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 429},
+ {"MovieId": "10152", "Title": "Dumb and Dumberer: When Harry Met Lloyd", "Casts": [], "PlotId": "10152",
+ "ThumbnailIds": ["/eizaKEnF108gQq89f1XsAyVxjq6.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.1,
+ "NumRating": 395}, {"MovieId": "2925", "Title": "The First Wives Club", "Casts": [], "PlotId": "2925",
+ "ThumbnailIds": ["/k4gb8CcRgvYM0XB0SBEiWzBhe3f.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 331},
+ {"MovieId": "103328", "Title": "Holy Motors", "Casts": [], "PlotId": "103328",
+ "ThumbnailIds": ["/d5amUFExQCqLkRpWQ6QspBtyWUe.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 418},
+ {"MovieId": "8010", "Title": "Highlander 2: The Quickening", "Casts": [], "PlotId": "8010",
+ "ThumbnailIds": ["/7W43CXQh6jF9NhnBnlA4BoaFp8W.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.6,
+ "NumRating": 352}, {"MovieId": "324786", "Title": "Hacksaw Ridge", "Casts": [], "PlotId": "324786",
+ "ThumbnailIds": ["/bndiUFfJxNd2fYx8XO610L9a07m.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.1, "NumRating": 6488},
+ {"MovieId": "424139", "Title": "Halloween", "Casts": [], "PlotId": "424139",
+ "ThumbnailIds": ["/lNkDYKmrVem1J0aAfCnQlJOCKnT.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 1913},
+ {"MovieId": "285", "Title": "Pirates of the Caribbean: At World's End", "Casts": [], "PlotId": "285",
+ "ThumbnailIds": ["/bXb00CkHqx7TPchTGG131sWV59y.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.1,
+ "NumRating": 8203}, {"MovieId": "584804", "Title": "Kleine Germanen", "Casts": [], "PlotId": "584804",
+ "ThumbnailIds": ["/uM6JkQRqAd4Q4MVAxAn8rayrI1.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 0.0, "NumRating": 0},
+ {"MovieId": "483157", "Title": "Morning Has Broken", "Casts": [], "PlotId": "483157",
+ "ThumbnailIds": [None], "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0, "NumRating": 0},
+ {"MovieId": "11560", "Title": "High Crimes", "Casts": [], "PlotId": "11560",
+ "ThumbnailIds": ["/adujr0eiutryiWmK8i0DPGTrOpU.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 321}, {"MovieId": "10956", "Title": "Joe Dirt", "Casts": [], "PlotId": "10956",
+ "ThumbnailIds": ["/8FKSjl4cEg4OEyWdCTjqAT8M6qv.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.4, "NumRating": 360},
+ {"MovieId": "585", "Title": "Monsters, Inc.", "Casts": [], "PlotId": "585",
+ "ThumbnailIds": ["/93Y9BGx8blzmZOPSoivkFfaifqU.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.8,
+ "NumRating": 10614},
+ {"MovieId": "1865", "Title": "Pirates of the Caribbean: On Stranger Tides", "Casts": [], "PlotId": "1865",
+ "ThumbnailIds": ["/wNUDAq5OUMOtxMlz64YaCp7gZma.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 8421}, {"MovieId": "8092", "Title": "This Boy\u2019s Life", "Casts": [], "PlotId": "8092",
+ "ThumbnailIds": ["/xs8ebrGRGu6Y9ebu2dJFdm9yaZP.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.1, "NumRating": 376},
+ {"MovieId": "569064", "Title": "Just Say Goodbye", "Casts": [], "PlotId": "569064",
+ "ThumbnailIds": ["/fHVlfvADGo9EMfEU6iMYsrTkusW.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0,
+ "NumRating": 0}, {"MovieId": "101173", "Title": "Coriolanus", "Casts": [], "PlotId": "101173",
+ "ThumbnailIds": ["/yE026snGPK1Cm61y6qmPdX5Hh8h.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.0, "NumRating": 210},
+ {"MovieId": "9087", "Title": "The American President", "Casts": [], "PlotId": "9087",
+ "ThumbnailIds": ["/yObOAYFIHXHkFPQ3jhgkN2ezaD.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 318}, {"MovieId": "9464", "Title": "Buffalo '66", "Casts": [], "PlotId": "9464",
+ "ThumbnailIds": ["/taVFuUhUWoX9YE7bb2bWkSPjC9P.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.2, "NumRating": 336},
+ {"MovieId": "11260", "Title": "Meet Dave", "Casts": [], "PlotId": "11260",
+ "ThumbnailIds": ["/gQKUAQGaNIsUhGL07zdhcrPyWdV.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.1,
+ "NumRating": 704}, {"MovieId": "8882", "Title": "Gomorrah", "Casts": [], "PlotId": "8882",
+ "ThumbnailIds": ["/6aVJi30jxywKXbrpcBD11AuVoKv.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.9, "NumRating": 519},
+ {"MovieId": "9515", "Title": "The Matador", "Casts": [], "PlotId": "9515",
+ "ThumbnailIds": ["/u4Pfeuz52JPBdxwxpMpyMApWltB.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.3,
+ "NumRating": 220}, {"MovieId": "34769", "Title": "Defendor", "Casts": [], "PlotId": "34769",
+ "ThumbnailIds": ["/bGwWUN9aqglafPmZRNlW3tmspuZ.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.4, "NumRating": 290},
+ {"MovieId": "11197", "Title": "Evil", "Casts": [], "PlotId": "11197",
+ "ThumbnailIds": ["/1ULoIF7u3hVoxOij4GMaqlYZRxc.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 142}, {"MovieId": "97051", "Title": "Would You Rather", "Casts": [], "PlotId": "97051",
+ "ThumbnailIds": ["/yOAU7HHVw4RrDGK97i6ll46JVfj.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.8, "NumRating": 456},
+ {"MovieId": "983", "Title": "The Man Who Would Be King", "Casts": [], "PlotId": "983",
+ "ThumbnailIds": ["/21BANIzXEKyZDUFOr9NdUEgP4EA.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.6,
+ "NumRating": 242}, {"MovieId": "445651", "Title": "The Darkest Minds", "Casts": [], "PlotId": "445651",
+ "ThumbnailIds": ["/94RaS52zmsqaiAe1TG20pdbJCZr.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.8, "NumRating": 1119},
+ {"MovieId": "447332", "Title": "A Quiet Place", "Casts": [], "PlotId": "447332",
+ "ThumbnailIds": ["/nAU74GmpUk7t5iklEp3bufwDq4n.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 5654}, {"MovieId": "11686", "Title": "Love and Death", "Casts": [], "PlotId": "11686",
+ "ThumbnailIds": ["/qRNisGLmcHoEvMjzcvVlcdCnOhO.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.8, "NumRating": 349},
+ {"MovieId": "426543", "Title": "The Nutcracker and the Four Realms", "Casts": [], "PlotId": "426543",
+ "ThumbnailIds": ["/tysit5m7HSEvf1wknPR0DeEhR7e.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 838}, {"MovieId": "646", "Title": "Dr. No", "Casts": [], "PlotId": "646",
+ "ThumbnailIds": ["/gRdfLVVf6FheOw6mw6wOsKhZG1l.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.0, "NumRating": 1578},
+ {"MovieId": "293", "Title": "A River Runs Through It", "Casts": [], "PlotId": "293",
+ "ThumbnailIds": ["/4nfpZVadu7nCJETENNhVWfR3okU.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.2,
+ "NumRating": 433}, {"MovieId": "7300", "Title": "One Fine Day", "Casts": [], "PlotId": "7300",
+ "ThumbnailIds": ["/yJETLNpYeEpDar7tJK1KedYGLgx.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.3, "NumRating": 308},
+ {"MovieId": "11374", "Title": "Edtv", "Casts": [], "PlotId": "11374",
+ "ThumbnailIds": ["/sF3YBtYn6yAHMPDbiC58WArIWMl.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.9,
+ "NumRating": 286},
+ {"MovieId": "63815", "Title": "Something Something... Unakkum Enakkum", "Casts": [], "PlotId": "63815",
+ "ThumbnailIds": ["/7bkeiY70hQZ3xVVNLwkmezpa0FC.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.9,
+ "NumRating": 9}, {"MovieId": "679", "Title": "Aliens", "Casts": [], "PlotId": "679",
+ "ThumbnailIds": ["/nORMXEkYEbzkU5WkMWMgRDJwjSZ.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.9, "NumRating": 5025},
+ {"MovieId": "11051", "Title": "The Last Temptation of Christ", "Casts": [], "PlotId": "11051",
+ "ThumbnailIds": ["/dutPJWBeQgdfjRqeKojqmyIdXFd.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.2,
+ "NumRating": 401}, {"MovieId": "238628", "Title": "Tangerines", "Casts": [], "PlotId": "238628",
+ "ThumbnailIds": ["/5PINEEPwh11MjUKw4M58NcVoCCb.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.8, "NumRating": 188},
+ {"MovieId": "268896", "Title": "Pacific Rim: Uprising", "Casts": [], "PlotId": "268896",
+ "ThumbnailIds": ["/v5HlmJK9bdeHxN2QhaFP1ivjX3U.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.9,
+ "NumRating": 2153},
+ {"MovieId": "631", "Title": "Sunrise: A Song of Two Humans", "Casts": [], "PlotId": "631",
+ "ThumbnailIds": ["/hEDMD8Lu7tMurqIglE8mo4GKBN.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.0,
+ "NumRating": 301}, {"MovieId": "38363", "Title": "Fair Game", "Casts": [], "PlotId": "38363",
+ "ThumbnailIds": ["/yE8uba2FkmFagMpeNNoo0cPyJ7D.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 379},
+ {"MovieId": "1880", "Title": "Red Dawn", "Casts": [], "PlotId": "1880",
+ "ThumbnailIds": ["/hVyeN1aFmqLsRK9VNElETYBDtnf.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.3,
+ "NumRating": 338}, {"MovieId": "11141", "Title": "Laws of Attraction", "Casts": [], "PlotId": "11141",
+ "ThumbnailIds": ["/raE9gFRS2oH4jBBjwIJvJresOQn.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.7, "NumRating": 197},
+ {"MovieId": "11158", "Title": "Honey I Blew Up the Kid", "Casts": [], "PlotId": "11158",
+ "ThumbnailIds": ["/gwda8CnPAfHGwygclGiKddt60fX.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.0,
+ "NumRating": 545}, {"MovieId": "10186", "Title": "The Rocker", "Casts": [], "PlotId": "10186",
+ "ThumbnailIds": ["/yQfng0iKQVuGDdI5JeoKPINlXuH.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.8, "NumRating": 274},
+ {"MovieId": "4520", "Title": "Sleuth", "Casts": [], "PlotId": "4520",
+ "ThumbnailIds": ["/5659i917XvBIf3i5fCPrOmCBja5.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 261}, {"MovieId": "1792", "Title": "Stuck on You", "Casts": [], "PlotId": "1792",
+ "ThumbnailIds": ["/gUbNzcIdKCoqp9amNFpFQfpBzrB.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.3, "NumRating": 324},
+ {"MovieId": "10122", "Title": "Flight of the Navigator", "Casts": [], "PlotId": "10122",
+ "ThumbnailIds": ["/69kD2bpkwSadpN30JB9vJrlz8HW.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 341}, {"MovieId": "9909", "Title": "Dangerous Minds", "Casts": [], "PlotId": "9909",
+ "ThumbnailIds": ["/y5Jee3QmYOlpqfaPPbfvtdVc5wj.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.5, "NumRating": 425},
+ {"MovieId": "119675", "Title": "Behind the Candelabra", "Casts": [], "PlotId": "119675",
+ "ThumbnailIds": ["/46J5VoqnneIz3hs50Ptk2o5bmXB.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 450}, {"MovieId": "185", "Title": "A Clockwork Orange", "Casts": [], "PlotId": "185",
+ "ThumbnailIds": ["/4sHeTAp65WrSSuc05nRBKddhBxO.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.2, "NumRating": 6563},
+ {"MovieId": "9966", "Title": "The Messengers", "Casts": [], "PlotId": "9966",
+ "ThumbnailIds": ["/x2Z5iXhHaoK22vwfxju5vq25apS.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.5,
+ "NumRating": 387}, {"MovieId": "64639", "Title": "Straw Dogs", "Casts": [], "PlotId": "64639",
+ "ThumbnailIds": ["/hSW5Msz5Cr6vUQxChPSPdxufnss.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.7, "NumRating": 246},
+ {"MovieId": "1406", "Title": "City Slickers", "Casts": [], "PlotId": "1406",
+ "ThumbnailIds": ["/tdOHDekHHGyjcR3Ay6WQ6uiFHoz.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 358},
+ {"MovieId": "259316", "Title": "Fantastic Beasts and Where to Find Them", "Casts": [], "PlotId": "259316",
+ "ThumbnailIds": ["/1M91Bt3oGspda75H9eLqYZkJzgO.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 11951},
+ {"MovieId": "512954", "Title": "Leaving Afghanistan", "Casts": [], "PlotId": "512954",
+ "ThumbnailIds": ["/aqNiKKr5fvJqCR4LEb1pcw48k1y.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0,
+ "NumRating": 0}, {"MovieId": "940", "Title": "The Lady Vanishes", "Casts": [], "PlotId": "940",
+ "ThumbnailIds": ["/edL8YmyR1BjICz3mp1fhUqSOPnF.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.6, "NumRating": 313},
+ {"MovieId": "11854", "Title": "Kuch Kuch Hota Hai", "Casts": [], "PlotId": "11854",
+ "ThumbnailIds": ["/hS1BvHAi29RXhKTyCoOEen0uU6j.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.6,
+ "NumRating": 162}, {"MovieId": "2771", "Title": "American Splendor", "Casts": [], "PlotId": "2771",
+ "ThumbnailIds": ["/qIJHt9PO13mAuFnHgrJUAq5p8Bf.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.3, "NumRating": 192},
+ {"MovieId": "704", "Title": "A Hard Day's Night", "Casts": [], "PlotId": "704",
+ "ThumbnailIds": ["/raJc1rxX0SQGzU1sRBAAFLkKGv1.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 268}, {"MovieId": "257344", "Title": "Pixels", "Casts": [], "PlotId": "257344",
+ "ThumbnailIds": ["/ktyVmIqfoaJ8w0gDSZyjhhOPpD6.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.6, "NumRating": 4370},
+ {"MovieId": "16642", "Title": "Days of Heaven", "Casts": [], "PlotId": "16642",
+ "ThumbnailIds": ["/rjw7tQbiBnbIeufyEt02oVickDM.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.7,
+ "NumRating": 391}, {"MovieId": "175", "Title": "The Big Blue", "Casts": [], "PlotId": "175",
+ "ThumbnailIds": ["/RgvRBAD5LTGsePMzjaqaNPkSYf.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.6, "NumRating": 691},
+ {"MovieId": "71668", "Title": "Piranha 3DD", "Casts": [], "PlotId": "71668",
+ "ThumbnailIds": ["/xua3x47piJTIRCVtVGjzXPDrbsN.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.2,
+ "NumRating": 498}, {"MovieId": "608", "Title": "Men in Black II", "Casts": [], "PlotId": "608",
+ "ThumbnailIds": ["/qWjRfBwr4VculczswwojXgoU0mq.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.3, "NumRating": 5582},
+ {"MovieId": "10909", "Title": "Kalifornia", "Casts": [], "PlotId": "10909",
+ "ThumbnailIds": ["/5KGQYEsJvQdWZQH6o1zIyzkZZRC.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 354}, {"MovieId": "287", "Title": "Bull Durham", "Casts": [], "PlotId": "287",
+ "ThumbnailIds": ["/wZwXLiR1cisTXaJiuBLvgkS5HWw.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.8, "NumRating": 255},
+ {"MovieId": "10758", "Title": "Waitress", "Casts": [], "PlotId": "10758",
+ "ThumbnailIds": ["/ux3SPTHHmJytSjWEaV7xlo9nbOZ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 228}, {"MovieId": "953", "Title": "Madagascar", "Casts": [], "PlotId": "953",
+ "ThumbnailIds": ["/2YiESGB68BGQSAFvfJxBi774sc4.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.8, "NumRating": 6092},
+ {"MovieId": "621", "Title": "Grease", "Casts": [], "PlotId": "621",
+ "ThumbnailIds": ["/iMHdFTrCYhue74sBnXkdO39AJ3R.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 3712}, {"MovieId": "11824", "Title": "Teen Wolf", "Casts": [], "PlotId": "11824",
+ "ThumbnailIds": ["/3TKJbKNpHvRP8YVnwbgfok41AAC.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.0, "NumRating": 475},
+ {"MovieId": "10414", "Title": "The Mighty Ducks", "Casts": [], "PlotId": "10414",
+ "ThumbnailIds": ["/4qXfjDlDEGuN3xRNawh4WZo5o96.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 335}, {"MovieId": "262500", "Title": "Insurgent", "Casts": [], "PlotId": "262500",
+ "ThumbnailIds": ["/6w1VjTPTjTaA5oNvsAg0y4H6bou.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.3, "NumRating": 6665},
+ {"MovieId": "9766", "Title": "Gridiron Gang", "Casts": [], "PlotId": "9766",
+ "ThumbnailIds": ["/8UlDsCPjAfg15L5Sgkcmy8rv1rg.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 366}, {"MovieId": "14072", "Title": "Rab Ne Bana Di Jodi", "Casts": [], "PlotId": "14072",
+ "ThumbnailIds": ["/m8x6I2qf3R98HtF4DmJXcdxCU64.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 101},
+ {"MovieId": "27585", "Title": "Rabbit Hole", "Casts": [], "PlotId": "27585",
+ "ThumbnailIds": ["/qBhDlqdLZzxqKIXO53vMjpqLgZu.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.8,
+ "NumRating": 287}, {"MovieId": "632", "Title": "Stalag 17", "Casts": [], "PlotId": "632",
+ "ThumbnailIds": ["/2PtDbmN7HAu4W5WqrUocBV8lgiZ.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.8, "NumRating": 239},
+ {"MovieId": "575766", "Title": "Hatsukoi: Otosan, Chibi ga Inaku Narimashita", "Casts": [],
+ "PlotId": "575766", "ThumbnailIds": ["/yMeXxSZ4IEitEWySboQaYAeEtXQ.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 0.0, "NumRating": 0},
+ {"MovieId": "372058", "Title": "Your Name.", "Casts": [], "PlotId": "372058",
+ "ThumbnailIds": ["/xq1Ugd62d23K2knRUx6xxuALTZB.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.6,
+ "NumRating": 4017}, {"MovieId": "225886", "Title": "Sex Tape", "Casts": [], "PlotId": "225886",
+ "ThumbnailIds": ["/2BMmIPGu5ZbTrwXwomdSUuTB2Ul.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.3, "NumRating": 2644},
+ {"MovieId": "593325", "Title": "Sodemacom Killer", "Casts": [], "PlotId": "593325",
+ "ThumbnailIds": ["/loTuS8ryototYPvGJaNsmlHCvIG.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0,
+ "NumRating": 0}, {"MovieId": "293167", "Title": "Kong: Skull Island", "Casts": [], "PlotId": "293167",
+ "ThumbnailIds": ["/r2517Vz9EhDhj88qwbDVj8DCRZN.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.3, "NumRating": 5937},
+ {"MovieId": "132316", "Title": "Jab Tak Hai Jaan", "Casts": [], "PlotId": "132316",
+ "ThumbnailIds": ["/oGoPWxz4v8HT8ydr2VdAa1OVohS.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.9,
+ "NumRating": 105}, {"MovieId": "11178", "Title": "My Sassy Girl", "Casts": [], "PlotId": "11178",
+ "ThumbnailIds": ["/jhlthtFzzEA8RYqRHy1y8dus4Q8.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.5, "NumRating": 266},
+ {"MovieId": "493922", "Title": "Hereditary", "Casts": [], "PlotId": "493922",
+ "ThumbnailIds": ["/lHV8HHlhwNup2VbpiACtlKzaGIQ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 2370}, {"MovieId": "64586", "Title": "Sleeping Beauty", "Casts": [], "PlotId": "64586",
+ "ThumbnailIds": ["/9fc7K82At056IlrK9dOzPSQYhSY.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.3, "NumRating": 297},
+ {"MovieId": "18570", "Title": "Food, Inc.", "Casts": [], "PlotId": "18570",
+ "ThumbnailIds": ["/gWYCM3YVYwZQcpfye6biAlPy9Xt.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 324}, {"MovieId": "553141", "Title": "The Head Hunter", "Casts": [], "PlotId": "553141",
+ "ThumbnailIds": ["/ol0DSLOIN8Rq1BcWDTsk6NNwas6.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.3, "NumRating": 35},
+ {"MovieId": "37414", "Title": "The Killer Inside Me", "Casts": [], "PlotId": "37414",
+ "ThumbnailIds": ["/9iaV9bNNFBxEWLjnMGvSmnNY4Uj.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.9,
+ "NumRating": 299}, {"MovieId": "129", "Title": "Spirited Away", "Casts": [], "PlotId": "129",
+ "ThumbnailIds": ["/oRvMaJOmapypFUcQqpgHMZA6qL9.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.5, "NumRating": 7271},
+ {"MovieId": "336843", "Title": "Maze Runner: The Death Cure", "Casts": [], "PlotId": "336843",
+ "ThumbnailIds": ["/2zYfzA3TBwrMC8tfFbpiTLODde0.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.1,
+ "NumRating": 3791}, {"MovieId": "273248", "Title": "The Hateful Eight", "Casts": [], "PlotId": "273248",
+ "ThumbnailIds": ["/fqe8JxDNO8B8QfOGTdjh6sPCdSC.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.7, "NumRating": 7887},
+ {"MovieId": "10720", "Title": "Down with Love", "Casts": [], "PlotId": "10720",
+ "ThumbnailIds": ["/c5fcbQM7uPObficCZjyTvwOcU1l.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 367}, {"MovieId": "438", "Title": "Cube Zero", "Casts": [], "PlotId": "438",
+ "ThumbnailIds": ["/m6Vn1rysx6vmgJuy78Ih9QxFoOy.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.7, "NumRating": 482},
+ {"MovieId": "14506", "Title": "The Adventures of Baron Munchausen", "Casts": [], "PlotId": "14506",
+ "ThumbnailIds": ["/75SlrEn0RCb2Ng18cCE82S00Hi5.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 356}, {"MovieId": "8398", "Title": "The Hitcher", "Casts": [], "PlotId": "8398",
+ "ThumbnailIds": ["/mIPCt79baN62Xv6TAEwLDxLUqBz.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.7, "NumRating": 422},
+ {"MovieId": "348", "Title": "Alien", "Casts": [], "PlotId": "348",
+ "ThumbnailIds": ["/2h00HrZs89SL3tXB4nbkiM7BKHs.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.1,
+ "NumRating": 7439}, {"MovieId": "812", "Title": "Aladdin", "Casts": [], "PlotId": "812",
+ "ThumbnailIds": ["/7f53XAE4nPiGe9XprpGAeWHuKPw.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.6, "NumRating": 6232},
+ {"MovieId": "401981", "Title": "Red Sparrow", "Casts": [], "PlotId": "401981",
+ "ThumbnailIds": ["/vLCogyfQGxVLDC1gqUdNAIkc29L.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.5,
+ "NumRating": 3321}, {"MovieId": "8491", "Title": "Weekend at Bernie's", "Casts": [], "PlotId": "8491",
+ "ThumbnailIds": ["/h3hjMREZEUPtDkZBiYDzLq0THk0.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.4, "NumRating": 414},
+ {"MovieId": "590927", "Title": "Solo cose belle", "Casts": [], "PlotId": "590927",
+ "ThumbnailIds": ["/htp3wtXbB0b4qrBmGLCpjvXKvrS.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0,
+ "NumRating": 0}, {"MovieId": "8386", "Title": "How High", "Casts": [], "PlotId": "8386",
+ "ThumbnailIds": ["/AdQDz4LF6BzFfmyIJkMtrj707BZ.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.2, "NumRating": 407},
+ {"MovieId": "447200", "Title": "Skyscraper", "Casts": [], "PlotId": "447200",
+ "ThumbnailIds": ["/5LYSsOPzuP13201qSzMjNxi8FxN.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 2089}, {"MovieId": "449443", "Title": "Den of Thieves", "Casts": [], "PlotId": "449443",
+ "ThumbnailIds": ["/AfybH6GbGFw1F9bcETe2yu25mIE.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.5, "NumRating": 1208},
+ {"MovieId": "2928", "Title": "Michael", "Casts": [], "PlotId": "2928",
+ "ThumbnailIds": ["/xof1HmoI3NrSrmzHByjK6W7dU8E.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.6,
+ "NumRating": 283}, {"MovieId": "9333", "Title": "Last Man Standing", "Casts": [], "PlotId": "9333",
+ "ThumbnailIds": ["/fkURS96D2ceuocZIBuyiIBGHilF.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.2, "NumRating": 399},
+ {"MovieId": "11078", "Title": "National Security", "Casts": [], "PlotId": "11078",
+ "ThumbnailIds": ["/5YFPkMgy5NCzUSYl5Ep8p0vK9qB.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.5,
+ "NumRating": 407}, {"MovieId": "857", "Title": "Saving Private Ryan", "Casts": [], "PlotId": "857",
+ "ThumbnailIds": ["/miDoEMlYDJhOCvxlzI0wZqBs9Yt.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.1, "NumRating": 8292},
+ {"MovieId": "376565", "Title": "The Duelist", "Casts": [], "PlotId": "376565",
+ "ThumbnailIds": ["/zlVkUY2bBJ5XYTDiry16u70oayQ.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.5,
+ "NumRating": 40}, {"MovieId": "10326", "Title": "Forever Young", "Casts": [], "PlotId": "10326",
+ "ThumbnailIds": ["/AvkLZ5EqehwUlJD93ZeobesDmHB.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.2, "NumRating": 401},
+ {"MovieId": "9777", "Title": "Proof", "Casts": [], "PlotId": "9777",
+ "ThumbnailIds": ["/eGVCVdbUR4gaAKPk1P4mar91qSX.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 335}, {"MovieId": "399360", "Title": "Alpha", "Casts": [], "PlotId": "399360",
+ "ThumbnailIds": ["/afdZAIcAQscziqVtsEoh2PwsYTW.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.9, "NumRating": 1117},
+ {"MovieId": "11797", "Title": "Fright Night", "Casts": [], "PlotId": "11797",
+ "ThumbnailIds": ["/jE0YbuFlmaZUWeVTyYNpzYXjIbn.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 515}, {"MovieId": "6691", "Title": "Priceless", "Casts": [], "PlotId": "6691",
+ "ThumbnailIds": ["/7GtzgK56NeIDzA6DksPihYVqTFm.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.4, "NumRating": 393},
+ {"MovieId": "21334", "Title": "Children of Heaven", "Casts": [], "PlotId": "21334",
+ "ThumbnailIds": ["/ik83L4ap4gYzlzGMsm2UqlIlsNe.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.0,
+ "NumRating": 210}, {"MovieId": "9589", "Title": "Christiane F.", "Casts": [], "PlotId": "9589",
+ "ThumbnailIds": ["/sM989XlpNVK8ITU8sGa1I4Fw1in.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.3, "NumRating": 549},
+ {"MovieId": "4515", "Title": "Lions for Lambs", "Casts": [], "PlotId": "4515",
+ "ThumbnailIds": ["/iLuo4swOCHF2a8BlaxXZGdqETUs.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 373}, {"MovieId": "8592", "Title": "Dick Tracy", "Casts": [], "PlotId": "8592",
+ "ThumbnailIds": ["/4UfKEGnj9jPWV11LNKBXTgge3We.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.1, "NumRating": 406},
+ {"MovieId": "76487", "Title": "The Devil Inside", "Casts": [], "PlotId": "76487",
+ "ThumbnailIds": ["/xClxu7PoHWMpCHHaqd3ZRdwOCnr.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.7,
+ "NumRating": 431}, {"MovieId": "23706", "Title": "All About Steve", "Casts": [], "PlotId": "23706",
+ "ThumbnailIds": ["/Ap0Lx5mv2tvE3LL8U3VQfCNdziL.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 4.8, "NumRating": 525},
+ {"MovieId": "626", "Title": "Un Chien Andalou", "Casts": [], "PlotId": "626",
+ "ThumbnailIds": ["/obvE7ElAvCUhKtWFwDSvNbPw9PV.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.5,
+ "NumRating": 540}, {"MovieId": "8046", "Title": "Gigli", "Casts": [], "PlotId": "8046",
+ "ThumbnailIds": ["/hy4HOGwrWc8et7jUJFzE9getbsp.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 3.6, "NumRating": 187},
+ {"MovieId": "19123", "Title": "Knock on Wood", "Casts": [], "PlotId": "19123",
+ "ThumbnailIds": ["/30dsG6akb4UYJiNDVdSTBSQsrTe.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.1,
+ "NumRating": 235}, {"MovieId": "19426", "Title": "Nights of Cabiria", "Casts": [], "PlotId": "19426",
+ "ThumbnailIds": ["/xF4oCG3PLNbcrtPZbqB3BtkIbKg.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.0, "NumRating": 235},
+ {"MovieId": "12877", "Title": "Dead Man's Shoes", "Casts": [], "PlotId": "12877",
+ "ThumbnailIds": ["/jGjlrDooATSSdmkrjH7lqFy6sWy.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.1,
+ "NumRating": 261}, {"MovieId": "545919", "Title": "Singel 39", "Casts": [], "PlotId": "545919",
+ "ThumbnailIds": ["/3SV996rVNm7KYe6HKDB6mYbu0Mp.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 0.0, "NumRating": 0},
+ {"MovieId": "350", "Title": "The Devil Wears Prada", "Casts": [], "PlotId": "350",
+ "ThumbnailIds": ["/8unCRm0LeiO0fM6skWAZy3ZfXR1.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.2,
+ "NumRating": 6394}, {"MovieId": "9516", "Title": "Menace II Society", "Casts": [], "PlotId": "9516",
+ "ThumbnailIds": ["/s1uQMgbK2tfaYltK6uDXU1xoYWA.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.4, "NumRating": 288},
+ {"MovieId": "769", "Title": "GoodFellas", "Casts": [], "PlotId": "769",
+ "ThumbnailIds": ["/hAPeXBdGDGmXRPj4OZZ0poH65Iu.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.4,
+ "NumRating": 5504}, {"MovieId": "508763", "Title": "A Dog's Way Home", "Casts": [], "PlotId": "508763",
+ "ThumbnailIds": ["/pZn87R7gtmMCGGO8KeaAfZDhXLg.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.5, "NumRating": 211},
+ {"MovieId": "9918", "Title": "Glory Road", "Casts": [], "PlotId": "9918",
+ "ThumbnailIds": ["/bGRSV5tStxDNPRLCewnOeeiZzrY.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 285}, {"MovieId": "51241", "Title": "Heartbeats", "Casts": [], "PlotId": "51241",
+ "ThumbnailIds": ["/ne84HBcScDVO9jBzgVv5qb7rZd0.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.4, "NumRating": 476},
+ {"MovieId": "302", "Title": "Swimming Pool", "Casts": [], "PlotId": "302",
+ "ThumbnailIds": ["/mHV60wGXvvkMR9ebVO5UWvxL716.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 272}, {"MovieId": "14048", "Title": "Man on Wire", "Casts": [], "PlotId": "14048",
+ "ThumbnailIds": ["/86kXY7u0HWSIK27RqRnu4r0cADz.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.5, "NumRating": 458},
+ {"MovieId": "310", "Title": "Bruce Almighty", "Casts": [], "PlotId": "310",
+ "ThumbnailIds": ["/lgYKHifMMLT8OxYObMKa8b4STsr.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 6093}, {"MovieId": "194662", "Title": "Birdman", "Casts": [], "PlotId": "194662",
+ "ThumbnailIds": ["/rSZs93P0LLxqlVEbI001UKoeCQC.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.4, "NumRating": 7597},
+ {"MovieId": "1487", "Title": "Hellboy", "Casts": [], "PlotId": "1487",
+ "ThumbnailIds": ["/3fAWzI9MUosggdGMu7EaDhn44m6.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 3930}, {"MovieId": "376540", "Title": "Mathilde", "Casts": [], "PlotId": "376540",
+ "ThumbnailIds": ["/kFXGRh6zIgX4e9x4ym4C4l5aVLo.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.4, "NumRating": 34},
+ {"MovieId": "10538", "Title": "Passenger 57", "Casts": [], "PlotId": "10538",
+ "ThumbnailIds": ["/mOh1t38TkW6JVg6ylQusKBPxxqa.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.7,
+ "NumRating": 416}, {"MovieId": "598073", "Title": "Daymohk", "Casts": [], "PlotId": "598073",
+ "ThumbnailIds": ["/tXx3ihWKWbOfzXUXkqo613PyETj.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 0.0, "NumRating": 0},
+ {"MovieId": "26280", "Title": "I Killed My Mother", "Casts": [], "PlotId": "26280",
+ "ThumbnailIds": ["/cxb02CLALt9He1Dt0BIDKj9fdvM.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.7,
+ "NumRating": 584},
+ {"MovieId": "12107", "Title": "Nutty Professor II: The Klumps", "Casts": [], "PlotId": "12107",
+ "ThumbnailIds": ["/r1WXXXtpNBsgCnCTdjT6ERxOcEV.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.8,
+ "NumRating": 661}, {"MovieId": "17927", "Title": "Fired Up!", "Casts": [], "PlotId": "17927",
+ "ThumbnailIds": ["/bHhLZIg9VKsconJdnwIoUTqakEG.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.1, "NumRating": 304},
+ {"MovieId": "12508", "Title": "Rock Star", "Casts": [], "PlotId": "12508",
+ "ThumbnailIds": ["/eMbsUnRfRciBGLclsqmICPOXbir.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 295}, {"MovieId": "14410", "Title": "Notorious", "Casts": [], "PlotId": "14410",
+ "ThumbnailIds": ["/lfKALQovRf46vVC12Rnl0mSRPyu.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 285},
+ {"MovieId": "9611", "Title": "Romy and Michele's High School Reunion", "Casts": [], "PlotId": "9611",
+ "ThumbnailIds": ["/fvgLTx3vM3dUQqytaWCRaTdAdrs.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 251}, {"MovieId": "10890", "Title": "Stripes", "Casts": [], "PlotId": "10890",
+ "ThumbnailIds": ["/nqfLX1bJLUlZnfflqpUIWrGQwSv.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 405},
+ {"MovieId": "11983", "Title": "Proof of Life", "Casts": [], "PlotId": "11983",
+ "ThumbnailIds": ["/qyNoOGCajNWH6heJ0kAmFFJtRQS.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 347},
+ {"MovieId": "14976", "Title": "Rachel Getting Married", "Casts": [], "PlotId": "14976",
+ "ThumbnailIds": ["/rq1z6yiYW1LTgXZhsyNgmtmOedK.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.1,
+ "NumRating": 291}, {"MovieId": "84306", "Title": "Liberal Arts", "Casts": [], "PlotId": "84306",
+ "ThumbnailIds": ["/c9Q2to1Jpq8a0Zqto6Mqc1ujLYn.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.3, "NumRating": 278},
+ {"MovieId": "7862", "Title": "The Counterfeiters", "Casts": [], "PlotId": "7862",
+ "ThumbnailIds": ["/bRQddrgVemZtFdnrPy9AxTpkhpj.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 235}, {"MovieId": "50797", "Title": "Burnt by the Sun", "Casts": [], "PlotId": "50797",
+ "ThumbnailIds": ["/e7bE9BiLyKaZtNF3Q2ro8gVCEQA.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.2, "NumRating": 60},
+ {"MovieId": "605", "Title": "The Matrix Revolutions", "Casts": [], "PlotId": "605",
+ "ThumbnailIds": ["/2aJvwc4zXqtVUDbEu62e14J0mhe.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 5033}, {"MovieId": "9957", "Title": "The Benchwarmers", "Casts": [], "PlotId": "9957",
+ "ThumbnailIds": ["/zQy6T5qV0lvLkkKi5S9VIYykSQD.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.4, "NumRating": 386},
+ {"MovieId": "1422", "Title": "The Departed", "Casts": [], "PlotId": "1422",
+ "ThumbnailIds": ["/tGLO9zw5ZtCeyyEWgbYGgsFxC6i.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.1,
+ "NumRating": 7729}, {"MovieId": "13973", "Title": "Choke", "Casts": [], "PlotId": "13973",
+ "ThumbnailIds": ["/lltyV3NNtzm4jaI5SfOYYsrr2Cz.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.2, "NumRating": 197},
+ {"MovieId": "359940", "Title": "Three Billboards Outside Ebbing, Missouri", "Casts": [],
+ "PlotId": "359940", "ThumbnailIds": ["/vgvw6w1CtcFkuXXn004S5wQsHRl.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 8.2, "NumRating": 5306},
+ {"MovieId": "13920", "Title": "Radio", "Casts": [], "PlotId": "13920",
+ "ThumbnailIds": ["/t4b6Ff8N0cvMBeYFtDNDm6xvyC9.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.9,
+ "NumRating": 289}, {"MovieId": "500664", "Title": "Upgrade", "Casts": [], "PlotId": "500664",
+ "ThumbnailIds": ["/8fDtXi6gVw8WUMWGT9XFz7YwkuE.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.4, "NumRating": 1254},
+ {"MovieId": "9410", "Title": "Great Expectations", "Casts": [], "PlotId": "9410",
+ "ThumbnailIds": ["/djLsfl3lm7BZKWsKfVZ2wM1ZMrP.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 244},
+ {"MovieId": "339964", "Title": "Valerian and the City of a Thousand Planets", "Casts": [],
+ "PlotId": "339964", "ThumbnailIds": ["/jfIpMh79fGRqYJ6PwZLCntzgxlF.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 4065},
+ {"MovieId": "425591", "Title": "I Don't Feel at Home in This World Anymore", "Casts": [],
+ "PlotId": "425591", "ThumbnailIds": ["/1stdUlXBc3nxqhdWvZ6wWWEbCQW.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.6, "NumRating": 582},
+ {"MovieId": "598549", "Title": "Low Budget (B.O.)", "Casts": [], "PlotId": "598549",
+ "ThumbnailIds": ["/qAu9YRsNdLXLH2Mg8mkpr5RBZ8f.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0,
+ "NumRating": 0}, {"MovieId": "449563", "Title": "Isn't It Romantic", "Casts": [], "PlotId": "449563",
+ "ThumbnailIds": ["/5xNBYXuv8wqiLVDhsfqCOr75DL7.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.4, "NumRating": 1509},
+ {"MovieId": "10069", "Title": "Stay Alive", "Casts": [], "PlotId": "10069",
+ "ThumbnailIds": ["/9iPpBKxpkgxHtsyGcnSlSN5KTu6.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.4,
+ "NumRating": 303}, {"MovieId": "9942", "Title": "Major League", "Casts": [], "PlotId": "9942",
+ "ThumbnailIds": ["/nD8hYPxh4Ph12dAsWiH3nQbqYDW.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.7, "NumRating": 332},
+ {"MovieId": "577109", "Title": "To Paris!", "Casts": [], "PlotId": "577109",
+ "ThumbnailIds": ["/m5mInKktHKCOob7mpKBN6zEZPAx.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0,
+ "NumRating": 0}, {"MovieId": "10823", "Title": "Children of the Corn", "Casts": [], "PlotId": "10823",
+ "ThumbnailIds": ["/votpiVmvic2ULYe74GES8CxYgp1.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.7, "NumRating": 401},
+ {"MovieId": "31175", "Title": "Soul Kitchen", "Casts": [], "PlotId": "31175",
+ "ThumbnailIds": ["/vUxpCAViRHmW4iIuAiwUxisQ5Wz.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.9,
+ "NumRating": 215}, {"MovieId": "363", "Title": "Head-On", "Casts": [], "PlotId": "363",
+ "ThumbnailIds": ["/5cPrr36unu3TTbdLdrfV1o7t8Sp.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.5, "NumRating": 232},
+ {"MovieId": "13250", "Title": "Butterfly on a Wheel", "Casts": [], "PlotId": "13250",
+ "ThumbnailIds": ["/wWe26HWNIogpYpGdvzizvBeH77p.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.5,
+ "NumRating": 238},
+ {"MovieId": "12140", "Title": "Ghost in the Shell 2: Innocence", "Casts": [], "PlotId": "12140",
+ "ThumbnailIds": ["/1KSMfpMWMzlvR2DN2fzv31vZ66B.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 364}, {"MovieId": "297761", "Title": "Suicide Squad", "Casts": [], "PlotId": "297761",
+ "ThumbnailIds": ["/e1mjopzAS2KNsvpbpahQ1a6SkSn.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.9, "NumRating": 13345},
+ {"MovieId": "8270", "Title": "The Lookout", "Casts": [], "PlotId": "8270",
+ "ThumbnailIds": ["/jSJca1tqlj08fGeqngTmr4UJ9ON.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.6,
+ "NumRating": 325}, {"MovieId": "44129", "Title": "The Company Men", "Casts": [], "PlotId": "44129",
+ "ThumbnailIds": ["/xP1FWgiUT1v1GGCwd94hXLaHyg8.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.5, "NumRating": 348},
+ {"MovieId": "55725", "Title": "Win Win", "Casts": [], "PlotId": "55725",
+ "ThumbnailIds": ["/s8Q2YqsJWXdZeT8Bpuzxic8UVd1.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.8,
+ "NumRating": 263}, {"MovieId": "10053", "Title": "When a Stranger Calls", "Casts": [], "PlotId": "10053",
+ "ThumbnailIds": ["/pYxd8DM4qBV24rJ9Jf84SYtIao1.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.5, "NumRating": 519},
+ {"MovieId": "11252", "Title": "Psycho", "Casts": [], "PlotId": "11252",
+ "ThumbnailIds": ["/5emaTTGS8rnLgZrBS98zqv1XWqE.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.1,
+ "NumRating": 457}, {"MovieId": "11894", "Title": "Curly Sue", "Casts": [], "PlotId": "11894",
+ "ThumbnailIds": ["/4NE36Icn6jrt7YHHXbB7zvNu9gq.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.9, "NumRating": 225},
+ {"MovieId": "2639", "Title": "Deconstructing Harry", "Casts": [], "PlotId": "2639",
+ "ThumbnailIds": ["/krKRthefSZlUjnzDvN4isqty0R7.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.3,
+ "NumRating": 345}, {"MovieId": "13446", "Title": "Withnail & I", "Casts": [], "PlotId": "13446",
+ "ThumbnailIds": ["/lXD5UR2dvXJF54AIBt8G2kDvYGk.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.4, "NumRating": 256},
+ {"MovieId": "354912", "Title": "Coco", "Casts": [], "PlotId": "354912",
+ "ThumbnailIds": ["/eKi8dIrr8voobbaGzDpe8w0PVbC.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.2,
+ "NumRating": 8460}, {"MovieId": "808", "Title": "Shrek", "Casts": [], "PlotId": "808",
+ "ThumbnailIds": ["/140ewbWv8qHStD3mlBDvvGd0Zvu.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.6, "NumRating": 8427},
+ {"MovieId": "315837", "Title": "Ghost in the Shell", "Casts": [], "PlotId": "315837",
+ "ThumbnailIds": ["/si1ZyELNHdPUZw4pXR5KjMIIsBF.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 5054}, {"MovieId": "570426", "Title": "Enterrados", "Casts": [], "PlotId": "570426",
+ "ThumbnailIds": ["/e8Hb4g3TEPFySwq5iao2IuUbEdb.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 0.0, "NumRating": 0},
+ {"MovieId": "10564", "Title": "Where the Heart Is", "Casts": [], "PlotId": "10564",
+ "ThumbnailIds": ["/rNP5NSdXzl6y5CuQ6Sh2JmEnvt5.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 338}, {"MovieId": "475888", "Title": "Tell It to the Bees", "Casts": [], "PlotId": "475888",
+ "ThumbnailIds": ["/Rj6zpHhMU1zFW3v28U4PSRSRgP.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.4, "NumRating": 12},
+ {"MovieId": "8088", "Title": "Broken Embraces", "Casts": [], "PlotId": "8088",
+ "ThumbnailIds": ["/9C6UiobINVB9AWKSazi0huHYyFY.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.1,
+ "NumRating": 283}, {"MovieId": "14052", "Title": "Revenge of the Nerds", "Casts": [], "PlotId": "14052",
+ "ThumbnailIds": ["/wCcMAx1J4JxBviEzy5Ay2FW79Nb.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.5, "NumRating": 324},
+ {"MovieId": "68924", "Title": "The Ice Storm", "Casts": [], "PlotId": "68924",
+ "ThumbnailIds": ["/e3j1Pn67bWos1pnBDE9GJNeAtcT.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.0,
+ "NumRating": 300}, {"MovieId": "427641", "Title": "Rampage", "Casts": [], "PlotId": "427641",
+ "ThumbnailIds": ["/vvrXUbiwFl4H8pJE7a4DzOoqVNB.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.2, "NumRating": 3054},
+ {"MovieId": "98", "Title": "Gladiator", "Casts": [], "PlotId": "98",
+ "ThumbnailIds": ["/6WBIzCgmDCYrqh64yDREGeDk9d3.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.1,
+ "NumRating": 9530}, {"MovieId": "43959", "Title": "Soul Surfer", "Casts": [], "PlotId": "43959",
+ "ThumbnailIds": ["/t7twsxCK3vIkn4z0w4WwHuVZPNy.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.0, "NumRating": 674},
+ {"MovieId": "14197", "Title": "My Sassy Girl", "Casts": [], "PlotId": "14197",
+ "ThumbnailIds": ["/PuHS7IRd3tRnkscBDq9AQ8wDHr.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.2,
+ "NumRating": 162}, {"MovieId": "13252", "Title": "Cleaner", "Casts": [], "PlotId": "13252",
+ "ThumbnailIds": ["/bhZ8LZXNOAMTpziYiE4ebn7est4.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.0, "NumRating": 342},
+ {"MovieId": "18079", "Title": "Nine Queens", "Casts": [], "PlotId": "18079",
+ "ThumbnailIds": ["/3LS2qYsmdYoSwQWJulK73lDfz52.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.7,
+ "NumRating": 281}, {"MovieId": "1368", "Title": "First Blood", "Casts": [], "PlotId": "1368",
+ "ThumbnailIds": ["/bbYNNEGLXrV3lJpHDg7CKaPscCb.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.3, "NumRating": 2723},
+ {"MovieId": "10843", "Title": "After Hours", "Casts": [], "PlotId": "10843",
+ "ThumbnailIds": ["/s5XkBqUMwE0wQv9NY0XERs64cgs.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.5,
+ "NumRating": 476}, {"MovieId": "45657", "Title": "The Ward", "Casts": [], "PlotId": "45657",
+ "ThumbnailIds": ["/cMApNxaSq4udLTYBAd755E8VJok.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.8, "NumRating": 601},
+ {"MovieId": "4967", "Title": "Keeping the Faith", "Casts": [], "PlotId": "4967",
+ "ThumbnailIds": ["/wbW4OnSNcuywgmJFdlvL8SvHx4.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.0,
+ "NumRating": 286}, {"MovieId": "37724", "Title": "Skyfall", "Casts": [], "PlotId": "37724",
+ "ThumbnailIds": ["/lQCkPLDxFONmgzrWLvq085v1g2d.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.1, "NumRating": 10328},
+ {"MovieId": "954", "Title": "Mission: Impossible", "Casts": [], "PlotId": "954",
+ "ThumbnailIds": ["/1PVKS17pIBFsIhgFws2uagPDNLW.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.9,
+ "NumRating": 4636}, {"MovieId": "10741", "Title": "Bobby", "Casts": [], "PlotId": "10741",
+ "ThumbnailIds": ["/a38CTs2mJ2uDHHH6mUJmuGH8RVI.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.6, "NumRating": 194},
+ {"MovieId": "598062", "Title": "Lumpkin, GA", "Casts": [], "PlotId": "598062",
+ "ThumbnailIds": ["/hLqsvhDrVK5my1fJVIZa0INrtB3.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0,
+ "NumRating": 0},
+ {"MovieId": "595409", "Title": "The Caring City", "Casts": [], "PlotId": "595409", "ThumbnailIds": [None],
+ "PhotoIds": [], "VideoIds": [], "AvgRating": 0.0, "NumRating": 0},
+ {"MovieId": "12177", "Title": "The Love Guru", "Casts": [], "PlotId": "12177",
+ "ThumbnailIds": ["/q3g4UFCMPqbKXfc3qMOsYQBOGbl.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 4.2,
+ "NumRating": 384},
+ {"MovieId": "1979", "Title": "Fantastic Four: Rise of the Silver Surfer", "Casts": [], "PlotId": "1979",
+ "ThumbnailIds": ["/fXpziQgnBnB4bLgjKhjTbLQumE5.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.5,
+ "NumRating": 4668}, {"MovieId": "458344", "Title": "Juliet, Naked", "Casts": [], "PlotId": "458344",
+ "ThumbnailIds": ["/tj4lbeWQBvPwGjadEAAjJdQolko.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.5, "NumRating": 118},
+ {"MovieId": "201088", "Title": "Blackhat", "Casts": [], "PlotId": "201088",
+ "ThumbnailIds": ["/sW3VEsulmxMlOmQwm0h7H7lZROi.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.3,
+ "NumRating": 1154}, {"MovieId": "10543", "Title": "Fear", "Casts": [], "PlotId": "10543",
+ "ThumbnailIds": ["/reA4wlTY9qGwryVftscFT2q0ZLK.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 6.2, "NumRating": 283},
+ {"MovieId": "38031", "Title": "You Will Meet a Tall Dark Stranger", "Casts": [], "PlotId": "38031",
+ "ThumbnailIds": ["/gbwJPEQZTSjjBAlv8DofNZVWiO.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.8,
+ "NumRating": 429}, {"MovieId": "11973", "Title": "Thirteen Days", "Casts": [], "PlotId": "11973",
+ "ThumbnailIds": ["/37yRUulECxF96eIok4ZXB8nRJ11.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 7.0, "NumRating": 314},
+ {"MovieId": "564", "Title": "The Mummy", "Casts": [], "PlotId": "564",
+ "ThumbnailIds": ["/yhIsVvcUm7QxzLfT6HW2wLf5ajY.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.7,
+ "NumRating": 4854}, {"MovieId": "13159", "Title": "Georgia Rule", "Casts": [], "PlotId": "13159",
+ "ThumbnailIds": ["/hEf4AlcN0wuVby5ysKf2RuckUgj.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 5.8, "NumRating": 229},
+ {"MovieId": "10849", "Title": "The Purple Rose of Cairo", "Casts": [], "PlotId": "10849",
+ "ThumbnailIds": ["/nWMT2WBzPtrXdjPlb28PFSrBGd0.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 7.4,
+ "NumRating": 407}, {"MovieId": "7095", "Title": "Jack", "Casts": [], "PlotId": "7095",
+ "ThumbnailIds": ["/nOgHa5ggt6vbmIuoNW7exrJQJq9.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 6.1, "NumRating": 596},
+ {"MovieId": "103", "Title": "Taxi Driver", "Casts": [], "PlotId": "103",
+ "ThumbnailIds": ["/ekstpH614fwDX8DUln1a2Opz0N8.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 8.1,
+ "NumRating": 5125}, {"MovieId": "26900", "Title": "The Bandit", "Casts": [], "PlotId": "26900",
+ "ThumbnailIds": ["/u1o4yJg0xkeVV8gfJL49CTZRBiu.jpg"], "PhotoIds": [],
+ "VideoIds": [], "AvgRating": 7.5, "NumRating": 110},
+ {"MovieId": "13279", "Title": "Lakeview Terrace", "Casts": [], "PlotId": "13279",
+ "ThumbnailIds": ["/iOnQK5te4gEhHpyZ5KpGCtbNn1B.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 5.9,
+ "NumRating": 478}, {"MovieId": "241251", "Title": "The Boy Next Door", "Casts": [], "PlotId": "241251",
+ "ThumbnailIds": ["/h28t2JNNGrZx0fIuAw8aHQFhIxR.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 4.4, "NumRating": 1371},
+ {"MovieId": "8987", "Title": "The River Wild", "Casts": [], "PlotId": "8987",
+ "ThumbnailIds": ["/8NFAQxZud5Kisw1sd9WxfBG54.jpg"], "PhotoIds": [], "VideoIds": [], "AvgRating": 6.4,
+ "NumRating": 396}, {"MovieId": "6309", "Title": "Flood", "Casts": [], "PlotId": "6309",
+ "ThumbnailIds": ["/s0F2iDMtj6uYQPqbjwCPlwug2O4.jpg"], "PhotoIds": [], "VideoIds": [],
+ "AvgRating": 5.5, "NumRating": 63}]
diff --git a/deathstar_movie_review/test_movie_review_demo.py b/deathstar_movie_review/test_movie_review_demo.py
new file mode 100644
index 0000000..78e3358
--- /dev/null
+++ b/deathstar_movie_review/test_movie_review_demo.py
@@ -0,0 +1,101 @@
+from cascade.dataflow.dataflow import Event, InitClass, InvokeMethod, OpNode
+from cascade.dataflow.optimization.dead_node_elim import dead_node_elimination
+from cascade.runtime.python_runtime import PythonClientSync, PythonRuntime
+from deathstar_movie_review.entities.compose_review import ComposeReview, compose_review_op
+from deathstar_movie_review.entities.user import User, user_op
+from deathstar_movie_review.entities.movie import MovieId, movie_id_op, movie_info_op, plot_op
+from deathstar_movie_review.entities.frontend import frontend_op, text_op, unique_id_op
+
+
+
+def test_deathstar_movie_demo_python():
+ print("starting")
+ runtime = PythonRuntime()
+
+ print(frontend_op.dataflow.to_dot())
+ dead_node_elimination([], [frontend_op])
+ print(frontend_op.dataflow.to_dot())
+
+ runtime.add_operator(compose_review_op)
+ runtime.add_operator(user_op)
+ runtime.add_operator(movie_info_op)
+ runtime.add_operator(movie_id_op)
+ runtime.add_operator(plot_op)
+ runtime.add_stateless_operator(frontend_op)
+ runtime.add_stateless_operator(unique_id_op)
+ runtime.add_stateless_operator(text_op)
+
+ runtime.run()
+ client = PythonClientSync(runtime)
+
+ init_user = OpNode(User, InitClass(), read_key_from="username")
+ username = "username_1"
+ user_data = {
+ "userId": "user1",
+ "FirstName": "firstname",
+ "LastName": "lastname",
+ "Username": username,
+ "Password": "****",
+ "Salt": "salt"
+ }
+ print("testing user create")
+ event = Event(init_user, {"username": username, "user_data": user_data}, None)
+ result = client.send(event)
+ assert isinstance(result, User) and result.username == username
+
+ print("testing compose review")
+ req_id = 1
+ movie_title = "Cars 2"
+ movie_id = 1
+
+ # make the review
+ init_compose_review = OpNode(ComposeReview, InitClass(), read_key_from="req_id")
+ event = Event(init_compose_review, {"req_id": req_id}, None)
+ result = client.send(event)
+ print("review made")
+
+
+ # make the movie
+ init_movie = OpNode(MovieId, InitClass(), read_key_from="title")
+ event = Event(init_movie, {"title": movie_title, "movie_id": movie_id}, None)
+ result = client.send(event)
+ print("movie made")
+
+ # compose the review
+ review_data = {
+ "review": req_id,
+ "user": username,
+ "title": movie_title,
+ "rating": 5,
+ "text": "good movie!"
+ }
+
+ event = Event(
+ frontend_op.dataflow.entry,
+ review_data,
+ frontend_op.dataflow)
+ result = client.send(event)
+ print(result)
+ print("review composed")
+
+
+ # read the review
+ get_review = OpNode(ComposeReview, InvokeMethod("get_data"), read_key_from="req_id")
+ event = Event(
+ get_review,
+ {"req_id": req_id},
+ None
+ )
+ result = client.send(event)
+ expected = {
+ "userId": user_data["userId"],
+ "movieId": movie_id,
+ "text": review_data["text"]
+ }
+ print(result, expected)
+ assert "review_id" in result
+ del result["review_id"] # randomly generated
+ assert result == expected
+
+ print("Success!")
+
\ No newline at end of file
diff --git a/deathstar_movie_review/workload_data.py b/deathstar_movie_review/workload_data.py
new file mode 100644
index 0000000..b533e36
--- /dev/null
+++ b/deathstar_movie_review/workload_data.py
@@ -0,0 +1,1008 @@
+charset = ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', 'a', 's',
+ 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'z', 'x', 'c', 'v', 'b', 'n', 'm', 'Q',
+ 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', 'A', 'S', 'D', 'F', 'G', 'H',
+ 'J', 'K', 'L', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '1', '2', '3', '4', '5',
+ '6', '7', '8', '9', '0']
+
+movie_titles = [
+ "Avengers: Endgame",
+ "Kamen Rider Heisei Generations FOREVER",
+ "Captain Marvel",
+ "Pokémon Detective Pikachu",
+ "Hellboy",
+ "After",
+ "Avengers: Infinity War",
+ "Doraemon the Movie: Nobita's Treasure Island",
+ "Cold Pursuit",
+ "Shazam!",
+ "What Men Want",
+ "Glass",
+ "The Avengers",
+ "Black Panther",
+ "How to Train Your Dragon: The Hidden World",
+ "Tolkien",
+ "Fighting with My Family",
+ "Guardians of the Galaxy Vol. 2",
+ "Avengers: Age of Ultron",
+ "The Prodigy",
+ "Cars",
+ "Thor: Ragnarok",
+ "The Wandering Earth",
+ "Extremely Wicked, Shockingly Evil and Vile",
+ "Pet Sematary",
+ "Logan",
+ "Guardians of the Galaxy",
+ "Fall in Love at First Kiss",
+ "Spider-Man: Into the Spider-Verse",
+ "Bumblebee",
+ "Thor",
+ "Aquaman",
+ "Ant-Man and the Wasp",
+ "The Lego Movie 2: The Second Part",
+ "Edge of Tomorrow",
+ "The Hustle",
+ "Dumbo",
+ "Redcon-1",
+ "Instant Family",
+ "Spider-Man: Homecoming",
+ "The Hobbit: The Battle of the Five Armies",
+ "Little",
+ "The Curse of La Llorona",
+ "Escape Room",
+ "Miss Bala",
+ "High Life",
+ "The Highwaymen",
+ "The Fate of the Furious",
+ "Iron Man",
+ "Captain America: The First Avenger",
+ "Captain America: Civil War",
+ "Us",
+ "Detective Conan: The Fist of Blue Sapphire",
+ "Fate/stay night: Heaven’s Feel II. lost butterfly",
+ "Robin Hood",
+ "Fantastic Beasts: The Crimes of Grindelwald",
+ "Thor: The Dark World",
+ "Alita: Battle Angel",
+ "Happy Death Day 2U",
+ "John Wick",
+ "Mile 22",
+ "Iron Man 2",
+ "Maggie",
+ "Venom",
+ "Harry Potter and the Philosopher's Stone",
+ "The Kid Who Would Be King",
+ "Pirates of the Caribbean: The Curse of the Black Pearl",
+ "Bohemian Rhapsody",
+ "The Mule",
+ "Ralph Breaks the Internet",
+ "The Lord of the Rings: The Fellowship of the Ring",
+ "Death Wish",
+ "John Wick: Chapter 3 – Parabellum",
+ "The Sisters Brothers",
+ "We Are Your Friends",
+ "Vaarikkuzhiyile Kolapathakam",
+ "The First Purge",
+ "Left Behind",
+ "Deadpool 2",
+ "BlacKkKlansman",
+ "Jigsaw",
+ "The Kissing Booth",
+ "Shaun the Sheep Movie",
+ "Under Siege 2: Dark Territory",
+ "Truth or Dare",
+ "Doctor Strange",
+ "The Lord of the Rings: The Return of the King",
+ "It's a Wonderful Life",
+ "Interstellar",
+ "Bad Times at the El Royale",
+ "A Madea Family Funeral",
+ "Long Shot",
+ "Suspiria",
+ "The Dark Knight",
+ "Trainspotting",
+ "Star Wars",
+ "Mortal Engines",
+ "Loving Vincent",
+ "Jurassic World: Fallen Kingdom",
+ "Velvet Buzzsaw",
+ "A Wrinkle in Time",
+ "The Lion King 1½",
+ "Spider-Man: Far from Home",
+ "Harry Potter and the Chamber of Secrets",
+ "Solo: A Star Wars Story",
+ "The Last Summer",
+ "Iron Man 3",
+ "Eighth Grade",
+ "Mandy",
+ "Die Hard",
+ "Home Alone 4",
+ "Adrift",
+ "The Predator",
+ "The Favourite",
+ "Overlord",
+ "Camp X-Ray",
+ "Mary Poppins Returns",
+ "Master Z: Ip Man Legacy",
+ "Re: Zero kara Hajimeru Isekai Seikatsu - Memory Snow",
+ "Scooby-Doo 2: Monsters Unleashed",
+ "The Transporter Refueled",
+ "Personal Shopper",
+ "Endless Love",
+ "Ant-Man",
+ "Polar",
+ "The Silence",
+ "The Professor and the Madman",
+ "12 Strong",
+ "Anon",
+ "Brightburn",
+ "Outlaw King",
+ "Texas Chainsaw 3D",
+ "The Upside",
+ "Breakthrough",
+ "The Emoji Movie",
+ "Green Book",
+ "A Bad Moms Christmas",
+ "Deadpool",
+ "Herbie Fully Loaded",
+ "Batman v Superman: Dawn of Justice",
+ "Bel Ami",
+ "A Hologram for the King",
+ "The Hunger Games: Mockingjay - Part 1",
+ "Planes",
+ "Wrong Turn 2: Dead End",
+ "Zodiac",
+ "The Curious Case of Benjamin Button",
+ "Wonder Woman",
+ "A Goofy Movie",
+ "The Incredible Hulk",
+ "San Andreas",
+ "Creed II",
+ "X-Men: Days of Future Past",
+ "Executive Decision",
+ "The Man Who Knew Too Much",
+ "Footloose",
+ "Justice League",
+ "47 Meters Down",
+ "A Star Is Born",
+ "Incredibles 2",
+ "The Square",
+ "Raw",
+ "Regression",
+ "Red Cliff",
+ "Star Wars: The Last Jedi",
+ "Hunter Killer",
+ "Chain Reaction",
+ "The Wind That Shakes the Barley",
+ "Last Knights",
+ "Avatar",
+ "The Matrix",
+ "I Spit on Your Grave 2",
+ "The 100 Year-Old Man Who Climbed Out the Window and Disappeared",
+ "2046",
+ "The Smurfs 2",
+ "Victoria",
+ "The Gunman",
+ "Batman Begins",
+ "I, Daniel Blake",
+ "Secret in Their Eyes",
+ "The Man Who Knew Infinity",
+ "The Past",
+ "The Belko Experiment",
+ "The Last Legion",
+ "Free State of Jones",
+ "Deadfall",
+ "The Shawshank Redemption",
+ "Anthropoid",
+ "Pitch Perfect 3",
+ "The Cold Light of Day",
+ "March of the Penguins",
+ "Popstar: Never Stop Never Stopping",
+ "The Cat Returns",
+ "The Zookeeper's Wife",
+ "Aladdin",
+ "Suburbicon",
+ "The Water Horse",
+ "Inception",
+ "The Return of the Living Dead",
+ "Odd Thomas",
+ "Jungle",
+ "Pulp Fiction",
+ "Hard Target",
+ "Iron Sky: The Coming Race",
+ "Terminator 2: Judgment Day",
+ "Legend",
+ "Twilight",
+ "Stealth",
+ "Battle of the Sexes",
+ "Split",
+ "Adore",
+ "Don't Say a Word",
+ "A Royal Affair",
+ "Salò, or the 120 Days of Sodom",
+ "Triple Frontier",
+ "The Rescuers",
+ "Blade Runner 2049",
+ "Tale of Tales",
+ "Taken 3",
+ "The Tale of Despereaux",
+ "It's Only the End of the World",
+ "Dark Places",
+ "Poms",
+ "Dragged Across Concrete",
+ "Unsane",
+ "15 Minutes",
+ "Batman: The Dark Knight Returns, Part 1",
+ "Ironclad",
+ "Taxi",
+ "Basic Instinct 2",
+ "Florence Foster Jenkins",
+ "Gone Girl",
+ "Rough Night",
+ "Schindler's List",
+ "Alexander and the Terrible, Horrible, No Good, Very Bad Day",
+ "The Great Mouse Detective",
+ "The Ring Two",
+ "Toni Erdmann",
+ "Forbidden Planet",
+ "The Lazarus Effect",
+ "The Triplets of Belleville",
+ "Heartbreaker",
+ "Someone Great",
+ "Veronica Mars",
+ "Byzantium",
+ "13 Sins",
+ "Mission: Impossible - Fallout",
+ "Far from the Madding Crowd",
+ "Charlie Says",
+ "'71",
+ "The Proposition",
+ "The Railway Man",
+ "A Ghost Story",
+ "The Choice",
+ "A Nightmare on Elm Street 4: The Dream Master",
+ "Bitter Moon",
+ "Arrival",
+ "Alvin and the Chipmunks: Chipwrecked",
+ "The Salesman",
+ "Soldier",
+ "The Lord of the Rings: The Two Towers",
+ "Sicario: Day of the Soldado",
+ "Mia and the White Lion",
+ "Vice",
+ "Harry Potter and the Deathly Hallows: Part 1",
+ "Zootopia",
+ "Whiteout",
+ "The Amazing Spider-Man",
+ "The Santa Clause 2",
+ "Five Feet Apart",
+ "Star Wars: The Force Awakens",
+ "No Man's Land",
+ "Whiplash",
+ "Hot Tub Time Machine 2",
+ "The Babysitter",
+ "The Paperboy",
+ "The Maze Runner",
+ "Jason X",
+ "Harry Potter and the Order of the Phoenix",
+ "Terms of Endearment",
+ "Frozen",
+ "Invasion of the Body Snatchers",
+ "The Divide",
+ "Brawl in Cell Block 99",
+ "Killing Season",
+ "Harry Potter and the Prisoner of Azkaban",
+ "Man Up",
+ "Missing Link",
+ "Friday the 13th Part 2",
+ "Force Majeure",
+ "Léon: The Professional",
+ "Suck Me Shakespeer",
+ "Teenage Mutant Ninja Turtles II: The Secret of the Ooze",
+ "Blood Father",
+ "Ivan's Childhood",
+ "The Garden of Words",
+ "All Dogs Go to Heaven",
+ "The Hobbit: An Unexpected Journey",
+ "Step Up All In",
+ "New Nightmare",
+ "Slow West",
+ "The Intruder",
+ "Arctic",
+ "Transsiberian",
+ "W.",
+ "Mongol: The Rise of Genghis Khan",
+ "Man of Tai Chi",
+ "Welcome to the Punch",
+ "Paranormal Activity: The Marked Ones",
+ "The Imitation Game",
+ "Police Academy 4: Citizens on Patrol",
+ "Fury",
+ "A Bridge Too Far",
+ "Citizenfour",
+ "Wolf Children",
+ "The Good Girl",
+ "To All the Boys I've Loved Before",
+ "The Nun",
+ "Black Sea",
+ "Flashdance",
+ "Where Eagles Dare",
+ "Rules of Engagement",
+ "Ladyhawke",
+ "The Meg",
+ "How to Train Your Dragon 2",
+ "Harry Potter and the Half-Blood Prince",
+ "Che: Part One",
+ "The Rover",
+ "Student of the Year 2",
+ "Destroyer",
+ "I Give It a Year",
+ "Two Days, One Night",
+ "The Ritual",
+ "Underdogs",
+ "Ida",
+ "Pirates of the Caribbean: Dead Man's Chest",
+ "Session 9",
+ "BloodRayne",
+ "Batman: The Killing Joke",
+ "Frankenstein",
+ "Kill the Messenger",
+ "The Imposter",
+ "Replicas",
+ "Maps to the Stars",
+ "Innerspace",
+ "Oliver & Company",
+ "Ladder 49",
+ "Snow White and the Seven Dwarfs",
+ "In the Name of the King: A Dungeon Siege Tale",
+ "War of the Worlds",
+ "Life",
+ "Child's Play 2",
+ "Sneakers",
+ "Fight Club",
+ "How to Train Your Dragon",
+ "Pathfinder",
+ "Colette",
+ "Joe",
+ "Batman: Gotham Knight",
+ "RoboCop 3",
+ "Drunken Master",
+ "Garfield: A Tail of Two Kitties",
+ "Miss & Mrs. Cops",
+ "The Fog",
+ "Pawn Sacrifice",
+ "The Equalizer 2",
+ "The Legend of Drunken Master",
+ "American Pie Presents: The Book of Love",
+ "Star Trek V: The Final Frontier",
+ "Blade",
+ "Tammy",
+ "Young Guns",
+ "Labor Day",
+ "Police Academy 3: Back in Training",
+ "Hellbound: Hellraiser II",
+ "The Reaping",
+ "The Axiom",
+ "Sexy Beast",
+ "The Big Short",
+ "The Human Centipede 2 (Full Sequence)",
+ "On the Basis of Sex",
+ "The Haunted Mansion",
+ "The Rebound",
+ "Ghosts of Mars",
+ "Nightcrawler",
+ "Absolute Power",
+ "Woman in Gold",
+ "Ready Player One",
+ "Disclosure",
+ "The Money Pit",
+ "Le Samouraï",
+ "Murder on the Orient Express",
+ "The Martian",
+ "Exposed",
+ "Flight of the Phoenix",
+ "Out of Africa",
+ "The Lunchbox",
+ "Terminator Genisys",
+ "The Purge: Anarchy",
+ "In the House",
+ "The General's Daughter",
+ "Under the Silver Lake",
+ "National Lampoon's European Vacation",
+ "The Muppet Christmas Carol",
+ "Hysteria",
+ "X-Men: Apocalypse",
+ "Operation Condor",
+ "Tomb Raider",
+ "Titanic",
+ "The Swan Princess",
+ "Very Bad Things",
+ "Suffragette",
+ "The Grudge 2",
+ "Scouts Guide to the Zombie Apocalypse",
+ "My Neighbor Totoro",
+ "The Tall Man",
+ "Magnum Force",
+ "Spider-Man 3",
+ "Asterix: The Secret of the Magic Potion",
+ "Dragonfly",
+ "French Kiss",
+ "The Devil's Own",
+ "Cinderella",
+ "3some",
+ "An American Tail",
+ "Dog Soldiers",
+ "Firewall",
+ "Dark Phoenix",
+ "Scanners",
+ "RV",
+ "2 Days in New York",
+ "Friends with Kids",
+ "An Officer and a Gentleman",
+ "102 Dalmatians",
+ "Freaks",
+ "Jurassic World",
+ "Under the Tuscan Sun",
+ "The Colony",
+ "Million Dollar Arm",
+ "Hard Boiled",
+ "Flash Gordon",
+ "Bound",
+ "All Quiet on the Western Front",
+ "Heist",
+ "The Three Musketeers",
+ "2010",
+ "Duck Soup",
+ "Mary Queen of Scots",
+ "Charlie Countryman",
+ "Mr. Right",
+ "Arthur and the Invisibles",
+ "Curse of the Golden Flower",
+ "Drinking Buddies",
+ "Harry Potter and the Goblet of Fire",
+ "Pathology",
+ "Over the Top",
+ "The Lion King",
+ "Driven",
+ "The Hunger Games: Mockingjay - Part 2",
+ "John Wick: Chapter 2",
+ "Sister Act 2: Back in the Habit",
+ "Halloween II",
+ "Crypto",
+ "The 12th Man",
+ "The East",
+ "Love of My Life",
+ "The Godfather",
+ "Eight Legged Freaks",
+ "Knocked Up",
+ "Dracula Untold",
+ "Excalibur",
+ "Somewhere",
+ "Pirates of the Caribbean: Dead Men Tell No Tales",
+ "Harry Potter and the Deathly Hallows: Part 2",
+ "Cannibal Holocaust",
+ "Old Dogs",
+ "The Equalizer",
+ "Redirected",
+ "Tokyo Story",
+ "James and the Giant Peach",
+ "Bride of Chucky",
+ "Maniac",
+ "The Lawnmower Man",
+ "Goal! II: Living the Dream",
+ "Everybody Wants Some!!",
+ "The Longest Day",
+ "Big Hero 6",
+ "Love Happens",
+ "Baby's Day Out",
+ "Happy Feet Two",
+ "Undisputed III: Redemption",
+ "Fred Claus",
+ "A Man Apart",
+ "Untraceable",
+ "Anatomy of a Murder",
+ "Wild and Free",
+ "Draft Day",
+ "The Medallion",
+ "Astro Boy",
+ "Song to Song",
+ "Neon Heart",
+ "Triple Threat",
+ "The Client",
+ "Starred Up",
+ "Cradle 2 the Grave",
+ "Unbreakable",
+ "Toy Story",
+ "I Am Mother",
+ "Salyut-7",
+ "First Man",
+ "New in Town",
+ "Marathon Man",
+ "DOA: Dead or Alive",
+ "Holmes & Watson",
+ "Lock Up",
+ "Mimic",
+ "The War of the Roses",
+ "Crash",
+ "Look Who's Talking Too",
+ "Spectral",
+ "3-Iron",
+ "Pale Rider",
+ "Wyatt Earp",
+ "The Dirt",
+ "Cocoon",
+ "Ichi the Killer",
+ "Big Momma's House 2",
+ "Beauty and the Beast",
+ "Breakdown",
+ "Lovelace",
+ "Take Me Home Tonight",
+ "Battleship Potemkin",
+ "Bullitt",
+ "Son of Saul",
+ "Transformers: The Last Knight",
+ "Duplicity",
+ "Double Impact",
+ "Far from Heaven",
+ "Striking Distance",
+ "The Shape of Water",
+ "Friday Night Lights",
+ "Annihilation",
+ "Dawn of the Planet of the Apes",
+ "The Crying Game",
+ "After the Sunset",
+ "The Dark Knight Rises",
+ "Parenthood",
+ "Passengers",
+ "The Gift",
+ "The Twelve Tasks of Asterix",
+ "After.Life",
+ "A Trip to the Moon",
+ "Don't Look Now",
+ "Playing for Keeps",
+ "Quills",
+ "Frantic",
+ "Guava Island",
+ "Turistas",
+ "Bulletproof Monk",
+ "High Plains Drifter",
+ "Man of Steel",
+ "McFarland, USA",
+ "Cat on a Hot Tin Roof",
+ "Goosebumps",
+ "Inland Empire",
+ "The Hunted",
+ "Enemy Mine",
+ "Marnie",
+ "National Lampoon's Loaded Weapon 1",
+ "The Four Feathers",
+ "Get Out",
+ "Trust",
+ "The Jewel of the Nile",
+ "My Own Private Idaho",
+ "Departures",
+ "Mirror",
+ "My Boss's Daughter",
+ "Goosebumps 2: Haunted Halloween",
+ "Fahrenheit 451",
+ "A Nightmare on Elm Street: The Dream Child",
+ "Crossroads",
+ "The Amazing Spider-Man 2",
+ "Rio Bravo",
+ "His Girl Friday",
+ "Kinsey",
+ "Howard the Duck",
+ "Red Sonja",
+ "Paperman",
+ "Sharknado",
+ "Vampires",
+ "Alone in the Dark",
+ "The Cranes Are Flying",
+ "The Prince & Me",
+ "Agent Cody Banks",
+ "Friday the 13th Part III",
+ "Dracula 2000",
+ "Bill & Ted's Bogus Journey",
+ "Lake Placid",
+ "War for the Planet of the Apes",
+ "The Corrupted",
+ "Nine",
+ "Did You Hear About the Morgans?",
+ "I Spy",
+ "The Ridiculous 6",
+ "Belle de Jour",
+ "The Libertine",
+ "The Extraordinary Adventures of Adèle Blanc-Sec",
+ "Working Girl",
+ "Forrest Gump",
+ "Captain America: The Winter Soldier",
+ "The Pink Panther",
+ "Spy Kids: All the Time in the World",
+ "The Right Stuff",
+ "See No Evil, Hear No Evil",
+ "U Turn",
+ "sex, lies, and videotape",
+ "Tideland",
+ "Down by Law",
+ "The Mission",
+ "Hellboy II: The Golden Army",
+ "3 Men and a Baby",
+ "Bad Words",
+ "Chariots of Fire",
+ "Spectre",
+ "People Like Us",
+ "Animal Kingdom",
+ "The Ledge",
+ "Copycat",
+ "Double Jeopardy",
+ "Super Mario Bros.",
+ "Bad Company",
+ "Striptease",
+ "Dolores Claiborne",
+ "Flyboys",
+ "Time Bandits",
+ "The Fan",
+ "Little Women",
+ "Away We Go",
+ "The Condemned",
+ "BASEketball",
+ "The Hitcher",
+ "A Long Way Down",
+ "Cabaret",
+ "The Normal Heart",
+ "Goya's Ghosts",
+ "The Odd Life of Timothy Green",
+ "Chalet Girl",
+ "The Cat in the Hat",
+ "Alice in Wonderland",
+ "Raising Helen",
+ "Problem Child 2",
+ "Westworld",
+ "Spider-Man",
+ "Widows",
+ "Show Me Love",
+ "The Player",
+ "Alien: Covenant",
+ "Shadow of a Doubt",
+ "Young & Beautiful",
+ "Vidocq",
+ "28 Days",
+ "Mad Max: Fury Road",
+ "The Perfect Date",
+ "The Barber of Siberia",
+ "Dr. Dolittle 2",
+ "Flypaper",
+ "Predator",
+ "The Wages of Fear",
+ "The Wolf of Wall Street",
+ "In the Land of Women",
+ "Enough Said",
+ "A Simple Favor",
+ "Once Upon a Deadpool",
+ "This Is It",
+ "The Descent: Part 2",
+ "Wild Target",
+ "Adam's Apples",
+ "Inside Out",
+ "Pride",
+ "The Grinch",
+ "Airplane II: The Sequel",
+ "The Fundamentals of Caring",
+ "The Tournament",
+ "Crimes and Misdemeanors",
+ "Stan & Ollie",
+ "The Big Year",
+ "Message in a Bottle",
+ "Kabhi Khushi Kabhie Gham",
+ "Rent",
+ "Naked Lunch",
+ "Without a Paddle",
+ "American Heist",
+ "True Story",
+ "The Jerk",
+ "Ratatouille",
+ "Zelig",
+ "The 51st State",
+ "The Karate Kid, Part III",
+ "Martha Marcy May Marlene",
+ "Moonstruck",
+ "Deuce Bigalow: European Gigolo",
+ "Raiders of the Lost Ark",
+ "Inspector Gadget",
+ "Blow",
+ "The Roommate",
+ "Smokey and the Bandit",
+ "Get Rich or Die Tryin'",
+ "Shutter Island",
+ "Unplanned",
+ "Tae Guk Gi: The Brotherhood of War",
+ "Invasion of the Body Snatchers",
+ "Teenage Mutant Ninja Turtles",
+ "Nim's Island",
+ "Aguirre: The Wrath of God",
+ "Dave",
+ "The Adventures of Robin Hood",
+ "Romper Stomper",
+ "The Producers",
+ "Ocean's Eight",
+ "Miracle",
+ "The Sentinel",
+ "Easy Virtue",
+ "Homeward Bound: The Incredible Journey",
+ "Boy Erased",
+ "Top Secret!",
+ "Airheads",
+ "Open Water",
+ "Prime",
+ "Harvey",
+ "THX 1138",
+ "Saved!",
+ "Lilya 4-ever",
+ "The Matrix Reloaded",
+ "In a Better World",
+ "Despite Everything",
+ "The Hobbit: The Desolation of Smaug",
+ "A Vigilante",
+ "Il grande spirito",
+ "The 39 Steps",
+ "The Battle of Algiers",
+ "The Cabinet of Dr. Caligari",
+ "Appaloosa",
+ "Re-Animator",
+ "The Passion of Joan of Arc",
+ "In the Loop",
+ "Bird Box",
+ "Serenity",
+ "The Golden Child",
+ "The Wrong Trousers",
+ "A Regular Woman",
+ "Men in Black 3",
+ "Mission: Impossible III",
+ "Fanboys",
+ "Welcome to Marwen",
+ "Stop! Or My Mom Will Shoot",
+ "Pariban : Idola Dari Tanah Jawa",
+ "The New Guy",
+ "Black Knight",
+ "House on Haunted Hill",
+ "Traitor",
+ "What Ever Happened to Baby Jane?",
+ "The Best Years of Our Lives",
+ "The Brothers Bloom",
+ "Black Rain",
+ "Fanny & Alexander",
+ "Perfect Blue",
+ "The Con Is On",
+ "Night at the Museum: Secret of the Tomb",
+ "The Mask",
+ "Ip Man 4",
+ "Men in Black",
+ "Float Like A Butterfly",
+ "The Peacemaker",
+ "WALL·E",
+ "Inglourious Basterds",
+ "Arthur",
+ "Coneheads",
+ "The Terminator",
+ "The Kid",
+ "Dumb and Dumberer: When Harry Met Lloyd",
+ "The First Wives Club",
+ "Holy Motors",
+ "Highlander 2: The Quickening",
+ "Hacksaw Ridge",
+ "Halloween",
+ "Pirates of the Caribbean: At World's End",
+ "Kleine Germanen",
+ "Morning Has Broken",
+ "High Crimes",
+ "Joe Dirt",
+ "Monsters, Inc.",
+ "Pirates of the Caribbean: On Stranger Tides",
+ "This Boy’s Life",
+ "Just Say Goodbye",
+ "Coriolanus",
+ "The American President",
+ "Buffalo '66",
+ "Meet Dave",
+ "Gomorrah",
+ "The Matador",
+ "Defendor",
+ "Evil",
+ "Would You Rather",
+ "The Man Who Would Be King",
+ "The Darkest Minds",
+ "A Quiet Place",
+ "Love and Death",
+ "The Nutcracker and the Four Realms",
+ "Dr. No",
+ "A River Runs Through It",
+ "One Fine Day",
+ "Edtv",
+ "Something Something... Unakkum Enakkum",
+ "Aliens",
+ "The Last Temptation of Christ",
+ "Tangerines",
+ "Pacific Rim: Uprising",
+ "Sunrise: A Song of Two Humans",
+ "Fair Game",
+ "Red Dawn",
+ "Laws of Attraction",
+ "Honey I Blew Up the Kid",
+ "The Rocker",
+ "Sleuth",
+ "Stuck on You",
+ "Flight of the Navigator",
+ "Dangerous Minds",
+ "Behind the Candelabra",
+ "A Clockwork Orange",
+ "The Messengers",
+ "Straw Dogs",
+ "City Slickers",
+ "Fantastic Beasts and Where to Find Them",
+ "Leaving Afghanistan",
+ "The Lady Vanishes",
+ "Kuch Kuch Hota Hai",
+ "American Splendor",
+ "A Hard Day's Night",
+ "Pixels",
+ "Days of Heaven",
+ "The Big Blue",
+ "Piranha 3DD",
+ "Men in Black II",
+ "Kalifornia",
+ "Bull Durham",
+ "Waitress",
+ "Madagascar",
+ "Grease",
+ "Teen Wolf",
+ "The Mighty Ducks",
+ "Insurgent",
+ "Gridiron Gang",
+ "Rab Ne Bana Di Jodi",
+ "Rabbit Hole",
+ "Stalag 17",
+ "Hatsukoi: Otosan, Chibi ga Inaku Narimashita",
+ "Your Name.",
+ "Sex Tape",
+ "Sodemacom Killer",
+ "Kong: Skull Island",
+ "Jab Tak Hai Jaan",
+ "My Sassy Girl",
+ "Hereditary",
+ "Sleeping Beauty",
+ "Food, Inc.",
+ "The Head Hunter",
+ "The Killer Inside Me",
+ "Spirited Away",
+ "Maze Runner: The Death Cure",
+ "The Hateful Eight",
+ "Down with Love",
+ "Cube Zero",
+ "The Adventures of Baron Munchausen",
+ "The Hitcher",
+ "Alien",
+ "Aladdin",
+ "Red Sparrow",
+ "Weekend at Bernie's",
+ "Solo cose belle",
+ "How High",
+ "Skyscraper",
+ "Den of Thieves",
+ "Michael",
+ "Last Man Standing",
+ "National Security",
+ "Saving Private Ryan",
+ "The Duelist",
+ "Forever Young",
+ "Proof",
+ "Alpha",
+ "Fright Night",
+ "Priceless",
+ "Children of Heaven",
+ "Christiane F.",
+ "Lions for Lambs",
+ "Dick Tracy",
+ "The Devil Inside",
+ "All About Steve",
+ "Un Chien Andalou",
+ "Gigli",
+ "Knock on Wood",
+ "Nights of Cabiria",
+ "Dead Man's Shoes",
+ "Singel 39",
+ "The Devil Wears Prada",
+ "Menace II Society",
+ "GoodFellas",
+ "A Dog's Way Home",
+ "Glory Road",
+ "Heartbeats",
+ "Swimming Pool",
+ "Man on Wire",
+ "Bruce Almighty",
+ "Birdman",
+ "Hellboy",
+ "Mathilde",
+ "Passenger 57",
+ "Daymohk",
+ "I Killed My Mother",
+ "Nutty Professor II: The Klumps",
+ "Fired Up!",
+ "Rock Star",
+ "Notorious",
+ "Romy and Michele's High School Reunion",
+ "Stripes",
+ "Proof of Life",
+ "Rachel Getting Married",
+ "Liberal Arts",
+ "The Counterfeiters",
+ "Burnt by the Sun",
+ "The Matrix Revolutions",
+ "The Benchwarmers",
+ "The Departed",
+ "Choke",
+ "Three Billboards Outside Ebbing, Missouri",
+ "Radio",
+ "Upgrade",
+ "Great Expectations",
+ "Valerian and the City of a Thousand Planets",
+ "I Don't Feel at Home in This World Anymore",
+ "Low Budget (B.O.)",
+ "Isn't It Romantic",
+ "Stay Alive",
+ "Major League",
+ "To Paris!",
+ "Children of the Corn",
+ "Soul Kitchen",
+ "Head-On",
+ "Butterfly on a Wheel",
+ "Ghost in the Shell 2: Innocence",
+ "Suicide Squad",
+ "The Lookout",
+ "The Company Men",
+ "Win Win",
+ "When a Stranger Calls",
+ "Psycho",
+ "Curly Sue",
+ "Deconstructing Harry",
+ "Withnail & I",
+ "Coco",
+ "Shrek",
+ "Ghost in the Shell",
+ "Enterrados",
+ "Where the Heart Is",
+ "Tell It to the Bees",
+ "Broken Embraces",
+ "Revenge of the Nerds",
+ "The Ice Storm",
+ "Rampage",
+ "Gladiator",
+ "Soul Surfer",
+ "My Sassy Girl",
+ "Cleaner",
+ "Nine Queens",
+ "First Blood",
+ "After Hours",
+ "The Ward",
+ "Keeping the Faith",
+ "Skyfall",
+ "Mission: Impossible",
+ "Bobby",
+ "Lumpkin, GA",
+ "The Caring City",
+ "The Love Guru",
+ "Fantastic Four: Rise of the Silver Surfer",
+ "Juliet, Naked",
+ "Blackhat",
+ "Fear",
+ "You Will Meet a Tall Dark Stranger",
+ "Thirteen Days",
+ "The Mummy",
+ "Georgia Rule",
+ "The Purple Rose of Cairo",
+ "Jack",
+ "Taxi Driver",
+ "The Bandit",
+ "Lakeview Terrace",
+ "The Boy Next Door",
+ "The River Wild",
+ "Flood"
+]
diff --git a/display_results.ipynb b/display_results.ipynb
new file mode 100644
index 0000000..f47e1d5
--- /dev/null
+++ b/display_results.ipynb
@@ -0,0 +1,3003 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ " event_id sent \\\n",
+ "0 701 Event(target=OpNode(User, InvokeMethod('login'... \n",
+ "1 702 Event(target=OpNode(User, InvokeMethod('login'... \n",
+ "2 703 Event(target=OpNode(User, InvokeMethod('login'... \n",
+ "3 704 Event(target=OpNode(User, InvokeMethod('login'... \n",
+ "4 705 Event(target=OpNode(User, InvokeMethod('login'... \n",
+ "\n",
+ " sent_t ret \\\n",
+ "0 (2, 1739358574443) EventResult(event_id=701, result=True, metadat... \n",
+ "1 (2, 1739358574443) EventResult(event_id=702, result=True, metadat... \n",
+ "2 (2, 1739358574443) EventResult(event_id=703, result=True, metadat... \n",
+ "3 (2, 1739358574443) EventResult(event_id=704, result=True, metadat... \n",
+ "4 (2, 1739358574443) EventResult(event_id=705, result=True, metadat... \n",
+ "\n",
+ " ret_t roundtrip flink_time deser_times loops \\\n",
+ "0 (2, 1739358574525) 0.075989 0.075989 [5.435943603515625e-05] 1 \n",
+ "1 (2, 1739358574503) 0.053916 0.053916 [3.266334533691406e-05] 1 \n",
+ "2 (2, 1739358574563) 0.113311 0.113311 [2.6702880859375e-05] 1 \n",
+ "3 (2, 1739358574563) 0.113266 0.113266 [2.0503997802734375e-05] 1 \n",
+ "4 (2, 1739358574563) 0.113229 0.113229 [1.5020370483398438e-05] 1 \n",
+ "\n",
+ " latency \n",
+ "0 82 \n",
+ "1 60 \n",
+ "2 120 \n",
+ "3 120 \n",
+ "4 120 \n"
+ ]
+ },
+ {
+ "data": {
+ "image/svg+xml": [
+ "\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import pandas as pd\n",
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "# Read the CSV file\n",
+ "df = pd.read_pickle('test2.pkl')\n",
+ "\n",
+ "# Display the first few rows of the dataframe to understand its structure\n",
+ "print(df.head())\n",
+ "\n",
+ "df['flink_time'] = df['flink_time'] * 1000\n",
+ "df.plot(x='event_id', y=['latency', 'flink_time'], kind='line')\n",
+ "plt.xlabel('Event ID')\n",
+ "plt.ylabel('Latency (ms)')\n",
+ "# plt.yscale('log')\n",
+ "plt.title('Latency per Event')\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ "\n",
+ "\n",
+ "\n"
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "def preprocess_data(pickle_file_path):\n",
+ " # Read the DataFrame from the pickle file\n",
+ " df = pd.read_pickle(pickle_file_path)\n",
+ "\n",
+ " # Multiply flink_time by 1000 to convert to milliseconds\n",
+ " df['flink_time'] = df['flink_time'] * 1000\n",
+ "\n",
+ " # Calculate the additional Kafka overhead\n",
+ " df['kafka_overhead'] = df['latency'] - df['flink_time']\n",
+ "\n",
+ " # Extract median values from df\n",
+ " flink_time_median = df['flink_time'].median()\n",
+ " latency_median = df['latency'].median()\n",
+ "\n",
+ " return {\n",
+ " 'flink_time_median': flink_time_median,\n",
+ " 'kafka_overhead_median': latency_median - flink_time_median\n",
+ " }\n",
+ "\n",
+ "# List of pickle files\n",
+ "titles = ['baseline', 'pipelined', 'parallel*']\n",
+ "pickle_files = ['compose_basic_1mps.pkl', 'compose_pipeline_1mps.pkl', 'compose_parallel_1mps.pkl']\n",
+ "\n",
+ "titles = ['baseline', 'parallel*']\n",
+ "pickle_files = ['reserve_serial_1mps.pkl', 'reserve_parallel.pkl']\n",
+ "\n",
+ "# Process each file and collect median data\n",
+ "all_median_data = []\n",
+ "for title, file in zip(titles, pickle_files):\n",
+ " median_values = preprocess_data(file)\n",
+ " all_median_data.append({\n",
+ " 'Metric': 'Flink Time',\n",
+ " 'Value': median_values['flink_time_median'],\n",
+ " 'Title': title\n",
+ " })\n",
+ " all_median_data.append({\n",
+ " 'Metric': 'Kafka Overhead',\n",
+ " 'Value': median_values['kafka_overhead_median'],\n",
+ " 'Title': title\n",
+ " })\n",
+ "\n",
+ "# Create a DataFrame for all median values\n",
+ "all_median_df = pd.DataFrame(all_median_data)\n",
+ "\n",
+ "# Sort titles based on 'Flink Time' in descending order\n",
+ "sorted_titles = (\n",
+ " all_median_df[all_median_df['Metric'] == 'Flink Time']\n",
+ " .sort_values(by='Value', ascending=False)['Title']\n",
+ ")\n",
+ "\n",
+ "# Pivot the DataFrame and reindex to maintain sorted order\n",
+ "pivot_df = (\n",
+ " all_median_df.pivot(index='Title', columns='Metric', values='Value')\n",
+ " .reindex(sorted_titles)\n",
+ ")\n",
+ "\n",
+ "# Plot\n",
+ "fig, ax = plt.subplots()\n",
+ "pivot_df.plot(kind='bar', stacked=True, color=['#1f77b4', '#ff7f0e'], ax=ax)\n",
+ "plt.ylabel('Time (ms)')\n",
+ "plt.title('Median Flink Time and Kafka Overhead')\n",
+ "plt.legend(['Flink Time', 'Kafka Overhead'])\n",
+ "plt.show()\n"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/src/cascade/dataflow/dataflow.py b/src/cascade/dataflow/dataflow.py
index 7e45dbd..bb5704b 100644
--- a/src/cascade/dataflow/dataflow.py
+++ b/src/cascade/dataflow/dataflow.py
@@ -1,6 +1,12 @@
-from abc import ABC
+from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Any, Callable, List, Optional, Type, Union
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ # Prevent circular imports
+ from cascade.dataflow.operator import StatelessOperator
+
class Operator(ABC):
pass
@@ -15,6 +21,9 @@ class InvokeMethod:
"""A method invocation of the underlying method indentifier."""
method_name: str
+ def __repr__(self) -> str:
+ return f"{self.__class__.__name__}('{self.method_name}')"
+
@dataclass
class Filter:
"""Filter by this function"""
@@ -23,10 +32,10 @@ class Filter:
@dataclass
class Node(ABC):
"""Base class for Nodes."""
+
id: int = field(init=False)
"""This node's unique id."""
-
_id_counter: int = field(init=False, default=0, repr=False)
outgoing_edges: list['Edge'] = field(init=False, default_factory=list, repr=False)
@@ -35,20 +44,121 @@ def __post_init__(self):
self.id = Node._id_counter
Node._id_counter += 1
+ @abstractmethod
+ def propogate(self, event: 'Event', targets: list['Node'], result: Any, **kwargs) -> list['Event']:
+ pass
+
@dataclass
class OpNode(Node):
"""A node in a `Dataflow` corresponding to a method call of a `StatefulOperator`.
+ A `Dataflow` may reference the same entity multiple times.
+ The `StatefulOperator` that this node belongs to is referenced by `entity`."""
+ entity: Type
+ method_type: Union[InitClass, InvokeMethod, Filter]
+ read_key_from: str
+ """Which variable to take as the key for this StatefulOperator"""
+
+ assign_result_to: Optional[str] = field(default=None)
+ """What variable to assign the result of this node to, if any."""
+ is_conditional: bool = field(default=False)
+ """Whether or not the boolean result of this node dictates the following path."""
+ collect_target: Optional['CollectTarget'] = field(default=None)
+ """Whether the result of this node should go to a CollectNode."""
+
+ def propogate(self, event: 'Event', targets: List[Node], result: Any) -> list['Event']:
+ return OpNode.propogate_opnode(self, event, targets, result)
+
+ @staticmethod
+ def propogate_opnode(node: Union['OpNode', 'StatelessOpNode'], event: 'Event', targets: list[Node],
+ result: Any) -> list['Event']:
+ num_targets = 1 if node.is_conditional else len(targets)
+
+ if event.collect_target is not None:
+ # Assign new collect targets
+ collect_targets = [
+ event.collect_target for i in range(num_targets)
+ ]
+ else:
+ # Keep old collect targets
+ collect_targets = [node.collect_target for i in range(num_targets)]
+
+ if node.is_conditional:
+ edges = event.dataflow.nodes[event.target.id].outgoing_edges
+ true_edges = [edge for edge in edges if edge.if_conditional]
+ false_edges = [edge for edge in edges if not edge.if_conditional]
+ if not (len(true_edges) == len(false_edges) == 1):
+ print(edges)
+ assert len(true_edges) == len(false_edges) == 1
+ target_true = true_edges[0].to_node
+ target_false = false_edges[0].to_node
+
+ assert len(collect_targets) == 1, "num targets should be 1"
+ ct = collect_targets[0]
+
+ return [Event(
+ target_true if result else target_false,
+ event.variable_map,
+ event.dataflow,
+ _id=event._id,
+ collect_target=ct,
+ metadata=event.metadata)
+ ]
+
+ else:
+ return [Event(
+ target,
+ event.variable_map,
+ event.dataflow,
+ _id=event._id,
+ collect_target=ct,
+ metadata=event.metadata)
+
+ for target, ct in zip(targets, collect_targets)]
+
+ def __repr__(self) -> str:
+ return f"{self.__class__.__name__}({self.entity.__name__}, {self.method_type})"
+
+@dataclass
+class StatelessOpNode(Node):
+ """A node in a `Dataflow` corresponding to a method call of a `StatelessOperator`.
+
A `Dataflow` may reference the same `StatefulOperator` multiple times.
The `StatefulOperator` that this node belongs to is referenced by `cls`."""
- operator: Operator
- method_type: Union[InitClass, InvokeMethod, Filter]
+ operator: 'StatelessOperator'
+ method_type: InvokeMethod
+ """Which variable to take as the key for this StatefulOperator"""
+
assign_result_to: Optional[str] = None
+ """What variable to assign the result of this node to, if any."""
is_conditional: bool = False
"""Whether or not the boolean result of this node dictates the following path."""
collect_target: Optional['CollectTarget'] = None
"""Whether the result of this node should go to a CollectNode."""
+ def propogate(self, event: 'Event', targets: List[Node], result: Any) -> List['Event']:
+ return OpNode.propogate_opnode(self, event, targets, result)
+
+@dataclass
+class DataflowNode(Node):
+ """A node in a `DataFlow` corresponding to the call of another dataflow"""
+ dataflow: 'DataFlow'
+ variable_rename: dict[str, str]
+
+ assign_result_to: Optional[str] = None
+ """What variable to assign the result of this node to, if any."""
+ is_conditional: bool = False
+ """Whether or not the boolean result of this node dictates the following path."""
+ collect_target: Optional['CollectTarget'] = None
+ """Whether the result of this node should go to a CollectNode."""
+
+ def propogate(self, event: 'Event', targets: List[Node], result: Any) -> List['Event']:
+ # remap the variable map of event into the new event
+
+ # add the targets as some sort of dataflow "exit nodes"
+ return self.dataflow
+
+
@dataclass
class SelectAllNode(Node):
"""A node type that will yield all items of an entity filtered by
@@ -57,7 +167,24 @@ class SelectAllNode(Node):
Think of this as executing `SELECT * FROM cls`"""
cls: Type
collect_target: 'CollectNode'
-
+ assign_key_to: str
+
+ def propogate(self, event: 'Event', targets: List[Node], result: Any, keys: list[str]):
+ targets = event.dataflow.get_neighbors(event.target)
+ assert len(targets) == 1
+ n = len(keys)
+ collect_targets = [
+ CollectTarget(self.collect_target, n, i)
+ for i in range(n)
+ ]
+ return [Event(
+ targets[0],
+ event.variable_map | {self.assign_key_to: key},
+ event.dataflow,
+ _id=event._id,
+ collect_target=ct,
+ metadata=event.metadata)
+ for ct, key in zip(collect_targets, keys)]
@dataclass
class CollectNode(Node):
@@ -70,6 +197,17 @@ class CollectNode(Node):
read_results_from: str
"""The variable name in the variable map that the individual items put their result in."""
+ def propogate(self, event: 'Event', targets: List[Node], result: Any, **kwargs) -> List['Event']:
+ collect_targets = [event.collect_target for i in range(len(targets))]
+ return [Event(
+ target,
+ event.variable_map,
+ event.dataflow,
+ _id=event._id,
+ collect_target=ct,
+ metadata=event.metadata)
+
+ for target, ct in zip(targets, collect_targets)]
@dataclass
class Edge():
@@ -94,35 +232,19 @@ class DataFlow:
item1[Item.get_price]
item2[Item.get_price]
user2[User.buy_items_1]
- merge{Merge}
+ collect{Collect}
user1-- item1_key -->item1;
user1-- item2_key -->item2;
- item1-- item1_price -->merge;
- item2-- item2_price -->merge;
- merge-- [item1_price, item2_price] -->user2;
- ```
-
- In code, one would write:
-
- ```py
- df = DataFlow("user.buy_items")
- n0 = OpNode(User, InvokeMethod("buy_items_0"))
- n1 = OpNode(Item, InvokeMethod("get_price"))
- n2 = OpNode(Item, InvokeMethod("get_price"))
- n3 = MergeNode()
- n4 = OpNode(User, InvokeMethod("buy_items_1"))
- df.add_edge(Edge(n0, n1))
- df.add_edge(Edge(n0, n2))
- df.add_edge(Edge(n1, n3))
- df.add_edge(Edge(n2, n3))
- df.add_edge(Edge(n3, n4))
+ item1-- item1_price -->collect;
+ item2-- item2_price -->collect;
+ collect-- [item1_price, item2_price] -->user2;
```
"""
def __init__(self, name: str):
self.name: str = name
self.adjacency_list: dict[int, list[int]] = {}
self.nodes: dict[int, Node] = {}
- self.entry: Node = None
+ self.entry: Union[Node, List[Node]] = None
def add_node(self, node: Node):
"""Add a node to the Dataflow graph if it doesn't already exist."""
@@ -134,24 +256,98 @@ def add_edge(self, edge: Edge):
"""Add an edge to the Dataflow graph. Nodes that don't exist will be added to the graph automatically."""
self.add_node(edge.from_node)
self.add_node(edge.to_node)
- self.adjacency_list[edge.from_node.id].append(edge.to_node.id)
- edge.from_node.outgoing_edges.append(edge)
+ if edge.to_node.id not in self.adjacency_list[edge.from_node.id]:
+ self.adjacency_list[edge.from_node.id].append(edge.to_node.id)
+ edge.from_node.outgoing_edges.append(edge)
+
+
+ def remove_edge(self, from_node: Node, to_node: Node):
+ """Remove an edge from the Dataflow graph."""
+ if from_node.id in self.adjacency_list and to_node.id in self.adjacency_list[from_node.id]:
+ # Remove from adjacency list
+ self.adjacency_list[from_node.id].remove(to_node.id)
+ # Remove from outgoing_edges
+ from_node.outgoing_edges = [
+ edge for edge in from_node.outgoing_edges if edge.to_node.id != to_node.id
+ ]
+
+ def remove_node(self, node: Node):
+ """Remove a node from the DataFlow graph and reconnect its parents to its children."""
+ if node.id not in self.nodes:
+ return # Node doesn't exist in the graph
+
+
+ if isinstance(node, OpNode) or isinstance(node, StatelessOpNode):
+ assert not node.is_conditional, "there's no clear way to remove a conditional node"
+ assert not node.assign_result_to, "can't delete node whose result is used"
+ assert not node.collect_target, "can't delete node which has a collect_target"
+
+ # Find parents (nodes that have edges pointing to this node)
+ parents = [parent_id for parent_id, children in self.adjacency_list.items() if node.id in children]
+
+ # Find children (nodes that this node points to)
+ children = self.adjacency_list[node.id]
+
+ # Set df entry
+ if self.entry == node:
+ print(children)
+ assert len(children) == 1, "cannot remove entry node if it doesn't exactly one child"
+ self.entry = self.nodes[children[0]]
+
+ # Connect each parent to each child
+ for parent_id in parents:
+ parent_node = self.nodes[parent_id]
+ for child_id in children:
+ child_node = self.nodes[child_id]
+ new_edge = Edge(parent_node, child_node)
+ self.add_edge(new_edge)
+
+ # Remove edges from parents to the node
+ for parent_id in parents:
+ parent_node = self.nodes[parent_id]
+ self.remove_edge(parent_node, node)
+
+ # Remove outgoing edges from the node
+ for child_id in children:
+ child_node = self.nodes[child_id]
+ self.remove_edge(node, child_node)
+
+
+
+ # Remove the node from the adjacency list and nodes dictionary
+ del self.adjacency_list[node.id]
+ del self.nodes[node.id]
+
def get_neighbors(self, node: Node) -> List[Node]:
"""Get the outgoing neighbors of this `Node`"""
return [self.nodes[id] for id in self.adjacency_list.get(node.id, [])]
-class Result(ABC):
- pass
+ def to_dot(self) -> str:
+ """Output the DataFlow graph in DOT (Graphviz) format."""
+ lines = [f"digraph {self.name} {{"]
-@dataclass
-class Arrived(Result):
- val: Any
+ # Add nodes
+ for node in self.nodes.values():
+ lines.append(f' {node.id} [label="{node}"];')
-@dataclass
-class NotArrived(Result):
- pass
+ # Add edges
+ for from_id, to_ids in self.adjacency_list.items():
+ for to_id in to_ids:
+ lines.append(f" {from_id} -> {to_id};")
+ lines.append("}")
+ return "\n".join(lines)
+
+ def generate_event(self, variable_map: dict[str, Any]) -> Union['Event', list['Event']]:
+ if isinstance(self.entry, list):
+ assert len(self.entry) != 0
+ first_event = Event(self.entry[0], variable_map, self)
+ id = first_event._id
+ return [first_event] + [Event(entry, variable_map, self, _id=id) for entry in self.entry[1:]]
+ else:
+ return Event(self.entry, variable_map, self)
+
@dataclass
class CollectTarget:
target_node: CollectNode
@@ -161,6 +357,13 @@ class CollectTarget:
result_idx: int
"""The index this result should be in the collected array."""
+def metadata_dict() -> dict:
+ return {
+ "in_t": None,
+ "deser_times": [],
+ "flink_time": 0
+ }
+
@dataclass
class Event():
"""An Event is an object that travels through the Dataflow graph."""
@@ -168,11 +371,6 @@ class Event():
target: 'Node'
"""The Node that this Event wants to go to."""
- key_stack: list[str]
- """The keys this event is concerned with.
- The top of the stack, i.e. `key_stack[-1]`, should always correspond to a key
- on the StatefulOperator of `target.cls` if `target` is an `OpNode`."""
-
variable_map: dict[str, Any]
"""A mapping of variable identifiers to values.
If `target` is an `OpNode` this map should include the variables needed for that method."""
@@ -188,6 +386,9 @@ class Event():
"""Tells each mergenode (key) how many events to merge on"""
_id_counter: int = field(init=False, default=0, repr=False)
+
+ metadata: dict = field(default_factory=metadata_dict)
+ """Event metadata containing, for example, timestamps for benchmarking"""
def __post_init__(self):
if self._id is None:
@@ -195,88 +396,27 @@ def __post_init__(self):
self._id = Event._id_counter
Event._id_counter += 1
- def propogate(self, key_stack, result) -> Union['EventResult', list['Event']]:
+ def propogate(self, result, select_all_keys: Optional[list[str]]=None) -> Union['EventResult', list['Event']]:
"""Propogate this event through the Dataflow."""
- # TODO: keys should be structs containing Key and Opnode (as we need to know the entity (cls) and method to invoke for that particular key)
- # the following method only works because we assume all the keys have the same entity and method
- if self.dataflow is None:# or len(key_stack) == 0:
- return EventResult(self._id, result)
+ if self.dataflow is None:
+ return EventResult(self._id, result, self.metadata)
targets = self.dataflow.get_neighbors(self.target)
if len(targets) == 0:
- return EventResult(self._id, result)
+ return EventResult(self._id, result, self.metadata)
else:
- keys = key_stack.pop()
- if not isinstance(keys, list):
- keys = [keys]
-
- collect_targets: list[Optional[CollectTarget]]
- # Events with SelectAllNodes need to be assigned a CollectTarget
- if isinstance(self.target, SelectAllNode):
- collect_targets = [
- CollectTarget(self.target.collect_target, len(keys), i)
- for i in range(len(keys))
- ]
- elif isinstance(self.target, OpNode) and self.target.collect_target is not None:
- collect_targets = [
- self.target.collect_target for i in range(len(keys))
- ]
- else:
- collect_targets = [self.collect_target for i in range(len(keys))]
-
- if isinstance(self.target, OpNode) and self.target.is_conditional:
- # In this case there will be two targets depending on the condition
-
- edges = self.dataflow.nodes[self.target.id].outgoing_edges
- true_edges = [edge for edge in edges if edge.if_conditional]
- false_edges = [edge for edge in edges if not edge.if_conditional]
- if not (len(true_edges) == len(false_edges) == 1):
- print(edges)
- assert len(true_edges) == len(false_edges) == 1
- target_true = true_edges[0].to_node
- target_false = false_edges[0].to_node
-
-
- return [Event(
- target_true if result else target_false,
- key_stack + [key],
- self.variable_map,
- self.dataflow,
- _id=self._id,
- collect_target=ct)
-
- for key, ct in zip(keys, collect_targets)]
-
- elif len(targets) == 1:
- # We assume that all keys need to go to the same target
- # this is only used for SelectAll propogation
-
- return [Event(
- targets[0],
- key_stack + [key],
- self.variable_map,
- self.dataflow,
- _id=self._id,
- collect_target=ct)
-
- for key, ct in zip(keys, collect_targets)]
+ current_node = self.target
+
+ if isinstance(current_node, SelectAllNode):
+ assert select_all_keys
+ return current_node.propogate(self, targets, result, select_all_keys)
else:
- # An event with multiple targets should have the same number of
- # keys in a list on top of its key stack
- assert len(targets) == len(keys)
- return [Event(
- target,
- key_stack + [key],
- self.variable_map,
- self.dataflow,
- _id=self._id,
- collect_target=ct)
-
- for target, key, ct in zip(targets, keys, collect_targets)]
+ return current_node.propogate(self, targets, result)
@dataclass
class EventResult():
event_id: int
- result: Any
\ No newline at end of file
+ result: Any
+ metadata: dict
\ No newline at end of file
diff --git a/src/cascade/dataflow/operator.py b/src/cascade/dataflow/operator.py
index 6fca4d6..56d3e45 100644
--- a/src/cascade/dataflow/operator.py
+++ b/src/cascade/dataflow/operator.py
@@ -10,20 +10,19 @@ class MethodCall(Generic[T], Protocol):
It corresponds to functions with the following signature:
```py
- def my_compiled_method(*args: Any, state: T, key_stack: list[str], **kwargs: Any) -> Any:
+ def my_compiled_method(variable_map: dict[str, Any], state: T) -> Any
...
```
- `T` corresponds to a Python class, which, if modified, should return as the 2nd item in the tuple.
-
- The first item in the returned tuple corresponds to the actual return value of the function.
+ The variable_map contains a mapping from identifiers (variables/keys) to
+ their values.
+ The state of type `T` corresponds to a Python class.
- The third item in the tuple corresponds to the `key_stack` which should be updated accordingly.
- Notably, a terminal function should pop a key off the `key_stack`, whereas a function that calls
- other functions should push the correct key(s) onto the `key_stack`.
+
+ The value returned corresponds to the value treturned by the function.
"""
- def __call__(self, variable_map: dict[str, Any], state: T, key_stack: list[str]) -> dict[str, Any]: ...
+ def __call__(self, variable_map: dict[str, Any], state: T) -> Any: ...
"""@private"""
@@ -61,14 +60,13 @@ def buy_item(self, item: Item) -> bool:
Here, the class could be turned into a StatefulOperator as follows:
```py
- def user_get_balance(variable_map: dict[str, Any], state: User, key_stack: list[str]):
- key_stack.pop()
+ def user_get_balance(variable_map: dict[str, Any], state: User):
return state.balance
- def user_buy_item_0(variable_map: dict[str, Any], state: User, key_stack: list[str]):
- key_stack.append(variable_map['item_key'])
+ def user_buy_item_0(variable_map: dict[str, Any], state: User):
+ pass
- def user_buy_item_1(variable_map: dict[str, Any], state: User, key_stack: list[str]):
+ def user_buy_item_1(variable_map: dict[str, Any], state: User):
state.balance -= variable_map['item_get_price']
return state.balance >= 0
@@ -100,19 +98,19 @@ def handle_init_class(self, *args, **kwargs) -> T:
"""Create an instance of the underlying class. Equivalent to `T.__init__(*args, **kwargs)`."""
return self.entity(*args, **kwargs)
- def handle_invoke_method(self, method: InvokeMethod, variable_map: dict[str, Any], state: T, key_stack: list[str]) -> dict[str, Any]:
+ def handle_invoke_method(self, method: InvokeMethod, variable_map: dict[str, Any], state: T) -> dict[str, Any]:
"""Invoke the method of the underlying class.
The `cascade.dataflow.dataflow.InvokeMethod` object must contain a method identifier
that exists on the underlying compiled class functions.
- The state `T` and key_stack is passed along to the function, and may be modified.
+ The state `T` is passed along to the function, and may be modified.
"""
- return self._methods[method.method_name](variable_map=variable_map, state=state, key_stack=key_stack)
+ return self._methods[method.method_name](variable_map=variable_map, state=state)
class StatelessMethodCall(Protocol):
- def __call__(self, variable_map: dict[str, Any], key_stack: list[str]) -> Any: ...
+ def __call__(self, variable_map: dict[str, Any]) -> Any: ...
"""@private"""
@@ -123,13 +121,13 @@ def __init__(self, methods: dict[str, StatelessMethodCall], dataflow: DataFlow)
self._methods = methods
self.dataflow = dataflow
- def handle_invoke_method(self, method: InvokeMethod, variable_map: dict[str, Any], key_stack: list[str]) -> dict[str, Any]:
+ def handle_invoke_method(self, method: InvokeMethod, variable_map: dict[str, Any]) -> dict[str, Any]:
"""Invoke the method of the underlying class.
The `cascade.dataflow.dataflow.InvokeMethod` object must contain a method identifier
that exists on the underlying compiled class functions.
- The state `T` and key_stack is passed along to the function, and may be modified.
+ The state `T` is passed along to the function, and may be modified.
"""
- return self._methods[method.method_name](variable_map=variable_map, key_stack=key_stack)
+ return self._methods[method.method_name](variable_map=variable_map)
diff --git a/src/cascade/dataflow/optimization/__init__.py b/src/cascade/dataflow/optimization/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/cascade/dataflow/optimization/dead_node_elim.py b/src/cascade/dataflow/optimization/dead_node_elim.py
new file mode 100644
index 0000000..d1a9d06
--- /dev/null
+++ b/src/cascade/dataflow/optimization/dead_node_elim.py
@@ -0,0 +1,63 @@
+from cascade.dataflow.dataflow import DataFlow, InvokeMethod
+from cascade.dataflow.operator import StatefulOperator, StatelessOperator
+import inspect
+
+def is_no_op(func):
+ # Get the source code of the function
+ source = inspect.getsource(func).strip()
+
+ # Extract the function body (skip the signature)
+ lines = source.splitlines()
+ if len(lines) < 2:
+ # A function with only a signature can't have a body
+ return False
+
+ # Check the body of the function
+ body = lines[1].strip()
+ # A valid no-op function body is either 'pass' or 'return'
+ return body in ("pass", "return")
+
+
+def dead_node_elimination(stateful_ops: list[StatefulOperator], stateless_ops: list[StatelessOperator]):
+ # Find dead functions
+ dead_func_names = set()
+ for op in stateful_ops:
+ for name, method in op._methods.items():
+ if is_no_op(method):
+ dead_func_names.add(name)
+
+ # Remove them from dataflows
+ for op in stateful_ops:
+ for dataflow in op.dataflows.values():
+ to_remove = []
+ for node in dataflow.nodes.values():
+ if hasattr(node, "method_type") and isinstance(node.method_type, InvokeMethod):
+ im: InvokeMethod = node.method_type
+ if im.method_name in dead_func_names:
+ to_remove.append(node)
+
+ for node in to_remove:
+ print(node)
+ dataflow.remove_node(node)
+ print(dataflow.to_dot())
+
+ # Find dead functions
+ dead_func_names = set()
+ for op in stateless_ops:
+ for name, method in op._methods.items():
+ if is_no_op(method):
+ dead_func_names.add(name)
+
+ # Remove them from dataflows
+ for op in stateless_ops:
+ to_remove = []
+ for node in op.dataflow.nodes.values():
+ if hasattr(node, "method_type") and isinstance(node.method_type, InvokeMethod):
+ im: InvokeMethod = node.method_type
+ if im.method_name in dead_func_names:
+ to_remove.append(node)
+
+ for node in to_remove:
+ op.dataflow.remove_node(node)
+
+
diff --git a/src/cascade/dataflow/optimization/parallelization.py b/src/cascade/dataflow/optimization/parallelization.py
new file mode 100644
index 0000000..79e3ea4
--- /dev/null
+++ b/src/cascade/dataflow/optimization/parallelization.py
@@ -0,0 +1,191 @@
+"""
+When is it safe to parallize nodes?
+
+-> When they don't affect each other
+-> The simpelest way of doing it could be to run individual dataflows in parallel
+(e.g. item.get_price() can run in parallel)
+-> must convey that we assume no side-affects, so the actual order of execution
+does not matter. could go deeper and give a spec.
+-> some instructions from the same dataflow could also be completed in parallel?
+maybe? like ILP. but might need to think of more contrived examples/do more
+advanced program analyis.
+
+From Control Flow to Dataflow
+3. Parallelizing Memory Operations
+- operations on different memory locatiosn need not be sequentialized
+- circulate a set of access tokens for each variable (=split function?)
+ - assume that every variable denotes a unique memory location (no aliasing)
+
+We have to be careful about certain types of parallelization. Consider the example:
+
+```
+# Calculate the average item price in basket: List[Item]
+n = 0
+p = 0
+for item in basket:
+ n += 1
+ p += item.price()
+return p / n
+```
+
+In this example we would want to parallelize the calls to item.price().
+But we have to make sure the calls to `n += 1` remains bounded to the number of
+items, even though there is no explicit data dependency.
+
+
+----
+
+
+There is another type of optimization we could look at.
+Suppose the following:
+
+```
+n = self.basket_size
+
+prices = [item.price() for item in self.basket]
+total_price = sum(prices)
+
+return total_price / n
+```
+
+In this case, the variable n is not needed in the list comprehension - unoptimized
+versions would generate an extra function instead of having the line be re-ordered
+into the bottom function. Instead, analyis of the variables each function needs
+access to would be a way to optimize these parts!
+
+--> Ask Soham about this!
+
+from "From control flow to dataflow"
+
+Consider the portion of control-flow graph between a node N and its *immediate
+postdominator* P. Every control-flow path starting at N ultimately ends up at P.
+Suppose that there is no reference to a variable x in any node on any path between
+N and P. It is clear that an access token for x that enters N may bypass this
+region of the graph altogether and go directly to P.
+
+
+----
+
+"Dataflow-Based Parallelization of Control-Flow Algorithms"
+
+loop invariant hoisting
+
+```
+i = 0
+while i < n:
+ x = y + z
+ a[i] = 6 * i + x * x
+ i += 1
+```
+
+can be transformed in
+
+```
+i = 0
+if i < n:
+ x = y + z # loop invariant 1
+ t1 = x * x # loop invariant 2
+ do { # do while loop needed in case the conditional has side effects
+ a[i] = 6 * i + t1
+ i += 1
+ } while i < n
+```
+
+this is achieved using reaching definitions analysis. In the paper:
+"It is a common optimization to pull those parts of a loop body
+that depend on only static datasets outside of the loop, and thus
+execute these parts only once [7 , 13 , 15 , 32 ]. However, launching
+new dataflow jobs for every iteration step prevents this optimiza-
+tion in the case of such binary operators where only one input is
+static. For example, if a static dataset is used as the build-side of
+a hash join, then the system should not rebuild the hash table at
+every iteration step. Labyrinth operators can keep such a hash
+table in their internal states between iteration steps. This is made
+possible by implementing iterations as a single cyclic dataflow
+job, where the lifetimes of operators span all the steps."
+Is there a similair example we could leverage for cascade? one with a "static dataset" as loop invariant?
+in spark, it's up to the programmer to .cache it
+
+
+In this paper, they also use an intermediate representation of one "basic block" per node.
+A "basic block" is a sequence of instructions that always execute one after the other,
+in other words contains no control flow. Control flow is defined by the edges in the
+dataflow graph that connect the nodes.
+
+There's also a slightly different focus of this paper. The focus is not on stateful
+dataflows, and obviously the application is still focused on bigdata-like applications,
+not ones were latency is key issue.
+
+
+Basic Blocks - Aho, A. V., Sethi, R., and Ullman, J. D. Compilers: principles, techniques, and
+tools, vol. 2. Addison-wesley Reading, 2007.
+SSA - Rastello, F. SSA-based Compiler Design. Springer Publishing Company,
+Incorporated, 2016.
+
+
+----
+
+ideas from "optimization of dataflows with UDFs:"
+
+we are basically making a DSL (integrated with python) which would allow for optimization
+of UDFs!! this optimization is inside the intermediate representation, and not directly in
+the target machine (similair to Emma, which uses a functional style *but* is a DSL (does it
+allow for arbitrary scala code?))
+
+---
+
+our program is essentially a compiler. this allows to take inspiration from existing
+works on compilation (which has existed for much longer than work on dataflows (?) -
+actually, dataflows were more popular initially when people didn't settle on the von Neumann architecture yet,
+see e.g. Monsoon (1990s) or the original control flow to dataflow paper. the popularisation and efficiency of tools
+such as drayadlinq, apache spark, apache flink has reinvigorated the attention towards dataflows).
+BUT compilers are often have hardware specific optimizations, based on the hardware instruction sets, or hardware-specifics
+such as optimization of register allocation, cache line considerations etc etc.
+The compiler in Cascade/other cf to df systems do not necessarily have the same considerations. This is because the backend
+is software rather than hardware (e.g. we use flink + kafka). Since software is generally a lot more flexible than hardware,
+we can instead impose certain considerations on the execution engine (which is now software, instead of a chip) rather than
+the other way around (e.g. SIMD introduced --> compiler optimizations introduced). (to be fair, compiler design has had major influences [citation needed] on CPU design, but the point is that hardware iteration
+is generally slower and more expensive than software iteration).
+
+
+---
+
+for certain optimizations, cascade assumes order of any side effects (such as file IO) does not matter.
+otherwise a lot of parallelization operations would become much more costly due to the necessary synchronization issues.
+
+---
+
+other optimization: code duplication
+
+this would remove nodes (assumption that less nodes = faster) at the cost of more computation per node.
+a common example is something like this:
+
+```
+cost = item.price()
+if cost > 30:
+ shipping_discount = discount_service.get_shipping_discount()
+ price = cost * shipping_discount
+else:
+ price = cost
+
+return price
+```
+
+in this case the "return price" could be duplicated accross the two branches,
+such that they don't need to return back to the function body.
+
+---
+
+other ideas:
+ https://en.wikipedia.org/wiki/Optimizing_compiler#Specific_techniques
+"""
+
+from cascade.dataflow.operator import StatefulOperator, StatelessOperator
+
+
+def node_parallelization(stateful_ops: list[StatefulOperator], stateless_ops: list[StatelessOperator]):
+ # Find parallelizable nodes
+ for op in stateful_ops:
+ for dataflow in op.dataflows.values():
+ pass
+ # Parallize them
\ No newline at end of file
diff --git a/src/cascade/dataflow/optimization/test_dead_node_elim.py b/src/cascade/dataflow/optimization/test_dead_node_elim.py
new file mode 100644
index 0000000..94b30af
--- /dev/null
+++ b/src/cascade/dataflow/optimization/test_dead_node_elim.py
@@ -0,0 +1,103 @@
+from typing import Any
+
+from cascade.dataflow.dataflow import DataFlow, Edge, InvokeMethod, OpNode
+from cascade.dataflow.operator import StatefulOperator
+from cascade.dataflow.optimization.dead_node_elim import dead_node_elimination
+from cascade.dataflow.optimization.dead_node_elim import is_no_op
+
+class User:
+ pass
+
+class Hotel:
+ pass
+
+class Flight:
+ pass
+
+def order_compiled_entry_0(variable_map: dict[str, Any], state: User) -> Any:
+ pass
+
+def order_compiled_entry_1(variable_map: dict[str, Any], state: User) -> Any:
+ pass
+
+def order_compiled_if_cond(variable_map: dict[str, Any], state: User) -> Any:
+ return variable_map["hotel_reserve"] and variable_map["flight_reserve"]
+
+def order_compiled_if_body(variable_map: dict[str, Any], state: User) -> Any:
+ return True
+
+def order_compiled_else_body(variable_map: dict[str, Any], state: User) -> Any:
+ return False
+
+user_op = StatefulOperator(
+ User,
+ {
+ "order_compiled_entry_0": order_compiled_entry_0,
+ "order_compiled_entry_1": order_compiled_entry_1,
+ "order_compiled_if_cond": order_compiled_if_cond,
+ "order_compiled_if_body": order_compiled_if_body,
+ "order_compiled_else_body": order_compiled_else_body
+ },
+ {}
+)
+
+# For now, the dataflow will be serial instead of parallel (calling hotel, then
+# flight). Future optimizations could try to automatically parallelize this.
+# There could definetly be some slight changes to this dataflow depending on
+# other optimizations aswell. (A naive system could have an empty first entry
+# before the first entity call).
+def user_order_df():
+ df = DataFlow("user_order")
+ n0 = OpNode(User, InvokeMethod("order_compiled_entry_0"), read_key_from="user_key")
+ n1 = OpNode(Hotel, InvokeMethod("reserve"), assign_result_to="hotel_reserve", read_key_from="hotel_key")
+ n2 = OpNode(User, InvokeMethod("order_compiled_entry_1"), read_key_from="user_key")
+ n3 = OpNode(Flight, InvokeMethod("reserve"), assign_result_to="flight_reserve", read_key_from="flight_key")
+ n4 = OpNode(User, InvokeMethod("order_compiled_if_cond"), is_conditional=True, read_key_from="user_key")
+ n5 = OpNode(User, InvokeMethod("order_compiled_if_body"), read_key_from="user_key")
+ n6 = OpNode(User, InvokeMethod("order_compiled_else_body"), read_key_from="user_key")
+
+ df.add_edge(Edge(n0, n1))
+ df.add_edge(Edge(n1, n2))
+ df.add_edge(Edge(n2, n3))
+ df.add_edge(Edge(n3, n4))
+ df.add_edge(Edge(n4, n5, if_conditional=True))
+ df.add_edge(Edge(n4, n6, if_conditional=False))
+
+ df.entry = n0
+ return df
+
+df = user_order_df()
+user_op.dataflows[df.name] = df
+
+def test_dead_node_elim():
+ print(user_op.dataflows[df.name].to_dot())
+
+ dead_node_elimination([user_op], [])
+
+ print(user_op.dataflows[df.name].to_dot())
+
+
+
+### TEST NO OP DETECTION ###
+
+def c1(variable_map: dict[str, Any]):
+ return (variable_map["dist"], variable_map["hotel_key"])
+
+def c2(variable_map: dict[str, Any]):
+ return None
+
+def c3(variable_map: dict[str, Any]):
+ return
+
+def c4(variable_map: dict[str, Any]):
+ pass
+
+def c5(variable_map: dict[str, Any]):
+ return True
+
+def test_no_op_detect():
+ assert not is_no_op(c1)
+ assert not is_no_op(c2)
+ assert is_no_op(c3)
+ assert is_no_op(c4)
+ assert not is_no_op(c5)
diff --git a/src/cascade/dataflow/test_dataflow.py b/src/cascade/dataflow/test_dataflow.py
index 1e29aad..a5b42af 100644
--- a/src/cascade/dataflow/test_dataflow.py
+++ b/src/cascade/dataflow/test_dataflow.py
@@ -12,14 +12,12 @@ def buy_item(self, item: 'DummyItem') -> bool:
self.balance -= item_price
return self.balance >= 0
-def buy_item_0_compiled(variable_map: dict[str, Any], state: DummyUser, key_stack: list[str]) -> dict[str, Any]:
- key_stack.append(variable_map["item_key"])
+def buy_item_0_compiled(variable_map: dict[str, Any], state: DummyUser):
return
-def buy_item_1_compiled(variable_map: dict[str, Any], state: DummyUser, key_stack: list[str]) -> dict[str, Any]:
- key_stack.pop()
+def buy_item_1_compiled(variable_map: dict[str, Any], state: DummyUser):
state.balance -= variable_map["item_price"]
- return {"user_postive_balance": state.balance >= 0}
+ return state.balance >= 0
class DummyItem:
def __init__(self, key: str, price: int):
@@ -29,10 +27,8 @@ def __init__(self, key: str, price: int):
def get_price(self) -> int:
return self.price
-def get_price_compiled(variable_map: dict[str, Any], state: DummyItem, key_stack: list[str]) -> dict[str, Any]:
- key_stack.pop() # final function
- variable_map["item_price"] = state.price
- # return {"item_price": state.price}
+def get_price_compiled(variable_map: dict[str, Any], state: DummyItem):
+ return state.price
################## TESTS #######################
@@ -46,53 +42,60 @@ def get_price_compiled(variable_map: dict[str, Any], state: DummyItem, key_stack
def test_simple_df_propogation():
df = DataFlow("user.buy_item")
- n1 = OpNode(DummyUser, InvokeMethod("buy_item_0_compiled"))
- n2 = OpNode(DummyItem, InvokeMethod("get_price"))
- n3 = OpNode(DummyUser, InvokeMethod("buy_item_1"))
+ n1 = OpNode(DummyUser, InvokeMethod("buy_item_0_compiled"), read_key_from="user_key")
+ n2 = OpNode(DummyItem, InvokeMethod("get_price"), read_key_from="item_key", assign_result_to="item_price")
+ n3 = OpNode(DummyUser, InvokeMethod("buy_item_1"), read_key_from="user_key")
df.add_edge(Edge(n1, n2))
df.add_edge(Edge(n2, n3))
user.buy_item(item)
- event = Event(n1, ["user"], {"item_key":"fork"}, df)
+ event = Event(n1, {"user_key": "user", "item_key":"fork"}, df)
# Manually propogate
- item_key = buy_item_0_compiled(event.variable_map, state=user, key_stack=event.key_stack)
- next_event = event.propogate(event.key_stack, item_key)
+ item_key = buy_item_0_compiled(event.variable_map, state=user)
+ next_event = event.propogate(event, item_key)
+ assert isinstance(next_event, list)
assert len(next_event) == 1
assert next_event[0].target == n2
- assert next_event[0].key_stack == ["user", "fork"]
event = next_event[0]
- item_price = get_price_compiled(event.variable_map, state=item, key_stack=event.key_stack)
- next_event = event.propogate(event.key_stack, item_price)
+ # manually add the price to the variable map
+ item_price = get_price_compiled(event.variable_map, state=item)
+ assert n2.assign_result_to
+ event.variable_map[n2.assign_result_to] = item_price
+ next_event = event.propogate(item_price)
+
+ assert isinstance(next_event, list)
assert len(next_event) == 1
assert next_event[0].target == n3
event = next_event[0]
- positive_balance = buy_item_1_compiled(event.variable_map, state=user, key_stack=event.key_stack)
- next_event = event.propogate(event.key_stack, None)
+ positive_balance = buy_item_1_compiled(event.variable_map, state=user)
+ next_event = event.propogate(None)
assert isinstance(next_event, EventResult)
def test_merge_df_propogation():
df = DataFlow("user.buy_2_items")
- n0 = OpNode(DummyUser, InvokeMethod("buy_2_items_0"))
+ n0 = OpNode(DummyUser, InvokeMethod("buy_2_items_0"), read_key_from="user_key")
n3 = CollectNode(assign_result_to="item_prices", read_results_from="item_price")
n1 = OpNode(
DummyItem,
InvokeMethod("get_price"),
assign_result_to="item_price",
- collect_target=CollectTarget(n3, 2, 0)
+ collect_target=CollectTarget(n3, 2, 0),
+ read_key_from="item_1_key"
)
n2 = OpNode(
DummyItem,
InvokeMethod("get_price"),
assign_result_to="item_price",
- collect_target=CollectTarget(n3, 2, 1)
+ collect_target=CollectTarget(n3, 2, 1),
+ read_key_from="item_2_key"
)
- n4 = OpNode(DummyUser, InvokeMethod("buy_2_items_1"))
+ n4 = OpNode(DummyUser, InvokeMethod("buy_2_items_1"), read_key_from="user_key")
df.add_edge(Edge(n0, n1))
df.add_edge(Edge(n0, n2))
df.add_edge(Edge(n1, n3))
@@ -100,25 +103,30 @@ def test_merge_df_propogation():
df.add_edge(Edge(n3, n4))
# User with key "foo" buys items with keys "fork" and "spoon"
- event = Event(n0, ["foo"], {"item_1_key": "fork", "item_2_key": "spoon"}, df)
+ event = Event(n0, {"user_key": "foo", "item_1_key": "fork", "item_2_key": "spoon"}, df)
# Propogate the event (without actually doing any calculation)
# Normally, the key_stack should've been updated by the runtime here:
- key_stack = ["foo", ["fork", "spoon"]]
- next_event = event.propogate(key_stack, None)
+ next_event = event.propogate(None)
+ assert isinstance(next_event, list)
assert len(next_event) == 2
assert next_event[0].target == n1
assert next_event[1].target == n2
event1, event2 = next_event
- next_event = event1.propogate(event1.key_stack, None)
+ next_event = event1.propogate(None)
+
+ assert isinstance(next_event, list)
assert len(next_event) == 1
assert next_event[0].target == n3
- next_event = event2.propogate(event2.key_stack, None)
+ next_event = event2.propogate(None)
+
+ assert isinstance(next_event, list)
assert len(next_event) == 1
assert next_event[0].target == n3
- final_event = next_event[0].propogate(next_event[0].key_stack, None)
+ final_event = next_event[0].propogate(None)
+ assert isinstance(final_event, list)
assert final_event[0].target == n4
diff --git a/src/cascade/runtime/flink_runtime.py b/src/cascade/runtime/flink_runtime.py
index fd74279..febfc83 100644
--- a/src/cascade/runtime/flink_runtime.py
+++ b/src/cascade/runtime/flink_runtime.py
@@ -1,9 +1,10 @@
+from abc import ABC
from dataclasses import dataclass
import os
import time
import uuid
import threading
-from typing import Literal, Optional, Type, Union
+from typing import Any, Literal, Optional, Type, Union
from pyflink.common.typeinfo import Types, get_gateway
from pyflink.common import Configuration, DeserializationSchema, SerializationSchema, WatermarkStrategy
from pyflink.datastream.connectors import DeliveryGuarantee
@@ -12,7 +13,7 @@
from pyflink.datastream.connectors.kafka import KafkaOffsetsInitializer, KafkaRecordSerializationSchema, KafkaSource, KafkaSink
from pyflink.datastream import ProcessFunction, StreamExecutionEnvironment
import pickle
-from cascade.dataflow.dataflow import Arrived, CollectNode, CollectTarget, Event, EventResult, Filter, InitClass, InvokeMethod, Node, NotArrived, OpNode, Result, SelectAllNode
+from cascade.dataflow.dataflow import CollectNode, CollectTarget, Event, EventResult, Filter, InitClass, InvokeMethod, Node, OpNode, SelectAllNode, StatelessOpNode
from cascade.dataflow.operator import StatefulOperator, StatelessOperator
from confluent_kafka import Producer, Consumer
import logging
@@ -36,6 +37,10 @@ class FlinkRegisterKeyNode(Node):
key: str
cls: Type
+ def propogate(self, event: Event, targets: list[Node], result: Any, **kwargs) -> list[Event]:
+ """A key registration event does not propogate."""
+ return []
+
class FlinkOperator(KeyedProcessFunction):
"""Wraps an `cascade.dataflow.datflow.StatefulOperator` in a KeyedProcessFunction so that it can run in Flink.
"""
@@ -49,51 +54,55 @@ def open(self, runtime_context: RuntimeContext):
self.state: ValueState = runtime_context.get_state(descriptor)
def process_element(self, event: Event, ctx: KeyedProcessFunction.Context):
- key_stack = event.key_stack
# should be handled by filters on this FlinkOperator
assert(isinstance(event.target, OpNode))
- assert(isinstance(event.target.operator, StatefulOperator))
- assert(event.target.operator.entity == self.operator.entity)
-
logger.debug(f"FlinkOperator {self.operator.entity.__name__}[{ctx.get_current_key()}]: Processing: {event.target.method_type}")
+
+ assert(event.target.entity == self.operator.entity)
+ key = ctx.get_current_key()
+ assert(key is not None)
+
if isinstance(event.target.method_type, InitClass):
# TODO: compile __init__ with only kwargs, and pass the variable_map itself
# otherwise, order of variable_map matters for variable assignment
result = self.operator.handle_init_class(*event.variable_map.values())
# Register the created key in FlinkSelectAllOperator
- register_key_event = Event(
- FlinkRegisterKeyNode(key_stack[-1], self.operator.entity),
- [],
- {},
- None,
- _id = event._id
- )
- logger.debug(f"FlinkOperator {self.operator.entity.__name__}[{ctx.get_current_key()}]: Registering key: {register_key_event}")
- yield register_key_event
+ # register_key_event = Event(
+ # FlinkRegisterKeyNode(key, self.operator.entity),
+ # {},
+ # None,
+ # _id = event._id
+ # )
+ # logger.debug(f"FlinkOperator {self.operator.entity.__name__}[{ctx.get_current_key()}]: Registering key: {register_key_event}")
+ # yield register_key_event
- # Pop this key from the key stack so that we exit
- key_stack.pop()
self.state.update(pickle.dumps(result))
elif isinstance(event.target.method_type, InvokeMethod):
- state = pickle.loads(self.state.value())
- result = self.operator.handle_invoke_method(event.target.method_type, variable_map=event.variable_map, state=state, key_stack=key_stack)
+ state = self.state.value()
+ if state is None:
+ # try to create the state if we haven't been init'ed
+ state = self.operator.handle_init_class(*event.variable_map.values())
+ else:
+ state = pickle.loads(state)
+
+ result = self.operator.handle_invoke_method(event.target.method_type, variable_map=event.variable_map, state=state)
# TODO: check if state actually needs to be updated
if state is not None:
self.state.update(pickle.dumps(state))
- elif isinstance(event.target.method_type, Filter):
- state = pickle.loads(self.state.value())
- result = event.target.method_type.filter_fn(event.variable_map, state)
- if not result:
- return
- result = event.key_stack[-1]
+ # elif isinstance(event.target.method_type, Filter):
+ # state = pickle.loads(self.state.value())
+ # result = event.target.method_type.filter_fn(event.variable_map, state)
+ # if not result:
+ # return
+ # result = event.key_stack[-1]
if event.target.assign_result_to is not None:
event.variable_map[event.target.assign_result_to] = result
- new_events = event.propogate(key_stack, result)
+ new_events = event.propogate(result)
if isinstance(new_events, EventResult):
logger.debug(f"FlinkOperator {self.operator.entity.__name__}[{ctx.get_current_key()}]: Returned {new_events}")
yield new_events
@@ -110,22 +119,20 @@ def __init__(self, operator: StatelessOperator) -> None:
def process_element(self, event: Event, ctx: KeyedProcessFunction.Context):
- key_stack = event.key_stack
# should be handled by filters on this FlinkOperator
- assert(isinstance(event.target, OpNode))
- assert(isinstance(event.target.operator, StatelessOperator))
+ assert(isinstance(event.target, StatelessOpNode))
logger.debug(f"FlinkStatelessOperator {self.operator.dataflow.name}[{event._id}]: Processing: {event.target.method_type}")
if isinstance(event.target.method_type, InvokeMethod):
- result = self.operator.handle_invoke_method(event.target.method_type, variable_map=event.variable_map, key_stack=key_stack)
+ result = self.operator.handle_invoke_method(event.target.method_type, variable_map=event.variable_map)
else:
raise Exception(f"A StatelessOperator cannot compute event type: {event.target.method_type}")
if event.target.assign_result_to is not None:
event.variable_map[event.target.assign_result_to] = result
- new_events = event.propogate(key_stack, result)
+ new_events = event.propogate(result)
if isinstance(new_events, EventResult):
logger.debug(f"FlinkStatelessOperator {self.operator.dataflow.name}[{event._id}]: Returned {new_events}")
yield new_events
@@ -157,11 +164,12 @@ def process_element(self, event: Event, ctx: 'ProcessFunction.Context'):
logger.debug(f"SelectAllOperator [{event.target.cls.__name__}]: Selecting all")
# Yield all the keys we now about
- event.key_stack.append(state)
+ new_keys = state
num_events = len(state)
# Propogate the event to the next node
- new_events = event.propogate(event.key_stack, None)
+ new_events = event.propogate(None, select_all_keys=new_keys)
+ assert isinstance(new_events, list), "SelectAll nodes shouldn't directly produce EventResults"
assert num_events == len(new_events)
logger.debug(f"SelectAllOperator [{event.target.cls.__name__}]: Propogated {num_events} events with target: {event.target.collect_target}")
@@ -169,6 +177,21 @@ def process_element(self, event: Event, ctx: 'ProcessFunction.Context'):
else:
raise Exception(f"Unexpected target for SelectAllOperator: {event.target}")
+class Result(ABC):
+ """A `Result` can be either `Arrived` or `NotArrived`. It is used in the
+ FlinkCollectOperator to determine whether all the events have completed
+ their computation."""
+ pass
+
+@dataclass
+class Arrived(Result):
+ val: Any
+
+@dataclass
+class NotArrived(Result):
+ pass
+
+
class FlinkCollectOperator(KeyedProcessFunction):
"""Flink implementation of a merge operator."""
def __init__(self):
@@ -208,7 +231,7 @@ def process_element(self, event: Event, ctx: KeyedProcessFunction.Context):
collection = [r.val for r in collection if r.val is not None] # type: ignore (r is of type Arrived)
event.variable_map[target_node.assign_result_to] = collection
- new_events = event.propogate(event.key_stack, collection)
+ new_events = event.propogate(collection)
self.collection.clear()
if isinstance(new_events, EventResult):
@@ -218,28 +241,6 @@ def process_element(self, event: Event, ctx: KeyedProcessFunction.Context):
logger.debug(f"FlinkCollectOp [{ctx.get_current_key()}]: Propogated {len(new_events)} new Events")
yield from new_events
-class FlinkMergeOperator(KeyedProcessFunction):
- """Flink implementation of a merge operator."""
- def __init__(self) -> None:
- self.other: ValueState = None # type: ignore (expect state to be initialised on .open())
-
- def open(self, runtime_context: RuntimeContext):
- descriptor = ValueStateDescriptor("merge_state", Types.PICKLED_BYTE_ARRAY())
- self.other = runtime_context.get_state(descriptor)
-
- def process_element(self, event: Event, ctx: KeyedProcessFunction.Context):
- other_map = self.other.value()
- logger.debug(f"FlinkMergeOp [{ctx.get_current_key()}]: Processing: {event}")
- if other_map == None:
- logger.debug(f"FlinkMergeOp [{ctx.get_current_key()}]: Saving variable map")
- self.other.update(event.variable_map)
- else:
- self.other.clear()
- logger.debug(f"FlinkMergeOp [{ctx.get_current_key()}]: Yielding merged variables")
- event.variable_map |= other_map
- new_event = event.propogate(event.key_stack, None)
- yield from new_event
-
class ByteSerializer(SerializationSchema, DeserializationSchema):
"""A custom serializer which maps bytes to bytes.
@@ -284,6 +285,35 @@ def __init__(self):
self, j_deserialization_schema=j_byte_string_schema
)
+def deserialize_and_timestamp(x) -> Event:
+ t1 = time.time()
+ e: Event = pickle.loads(x)
+ t2 = time.time()
+ if e.metadata["in_t"] is None:
+ e.metadata["in_t"] = t1
+ e.metadata["current_in_t"] = t1
+ e.metadata["deser_times"].append(t2 - t1)
+ return e
+
+def timestamp_event(e: Event) -> Event:
+ t1 = time.time()
+ try:
+ e.metadata["flink_time"] += t1 - e.metadata["current_in_t"]
+ except KeyError:
+ pass
+ return e
+
+def timestamp_result(e: EventResult) -> EventResult:
+ t1 = time.time()
+ e.metadata["out_t"] = t1
+ e.metadata["flink_time"] += t1 - e.metadata["current_in_t"]
+ e.metadata["loops"] = len(e.metadata["deser_times"])
+ e.metadata["roundtrip"] = e.metadata["out_t"] - e.metadata["in_t"]
+ return e
+
+def debug(x, msg=""):
+ logger.debug(msg)
+ return x
class FlinkRuntime():
"""A Runtime that runs Dataflows on Flink."""
@@ -337,6 +367,10 @@ def init(self, kafka_broker="localhost:9092", bundle_time=1, bundle_size=5, para
config.set_integer("python.fn-execution.bundle.time", bundle_time)
config.set_integer("python.fn-execution.bundle.size", bundle_size)
+ # optimize for low latency
+ config.set_integer("taskmanager.memory.managed.size", 0)
+ config.set_integer("execution.buffer-timeout", 0)
+
self.env = StreamExecutionEnvironment.get_execution_environment(config)
if parallelism:
self.env.set_parallelism(parallelism)
@@ -401,11 +435,13 @@ def init(self, kafka_broker="localhost:9092", bundle_time=1, bundle_size=5, para
WatermarkStrategy.no_watermarks(),
"Kafka Source"
)
- .map(lambda x: pickle.loads(x))
+ .map(lambda x: deserialize_and_timestamp(x))
+ # .map(lambda x: debug(x, msg=f"entry: {x}"))
.name("DESERIALIZE")
# .filter(lambda e: isinstance(e, Event)) # Enforced by `send` type safety
)
-
+
+ """REMOVE SELECT ALL NODES
# Events with a `SelectAllNode` will first be processed by the select
# all operator, which will send out multiple other Events that can
# then be processed by operators in the same steam.
@@ -415,33 +451,26 @@ def init(self, kafka_broker="localhost:9092", bundle_time=1, bundle_size=5, para
.key_by(lambda e: e.target.cls)
.process(FlinkSelectAllOperator()).name("SELECT ALL OP")
)
- """Stream that ingests events with an `SelectAllNode` or `FlinkRegisterKeyNode`"""
+ # Stream that ingests events with an `SelectAllNode` or `FlinkRegisterKeyNode`
not_select_all_stream = (
event_stream.filter(lambda e:
not (isinstance(e.target, SelectAllNode) or isinstance(e.target, FlinkRegisterKeyNode)))
)
- event_stream_2 = select_all_stream.union(not_select_all_stream)
-
- operator_stream = event_stream_2.filter(lambda e: isinstance(e.target, OpNode)).name("OPERATOR STREAM")
-
- self.stateful_op_stream = (
- operator_stream
- .filter(lambda e: isinstance(e.target.operator, StatefulOperator))
- )
+ operator_stream = select_all_stream.union(not_select_all_stream)
+ """
- self.stateless_op_stream = (
- operator_stream
- .filter(lambda e: isinstance(e.target.operator, StatelessOperator))
- )
+ self.stateful_op_stream = event_stream
+ self.stateless_op_stream = event_stream
- self.merge_op_stream = (
- event_stream.filter(lambda e: isinstance(e.target, CollectNode))
- .key_by(lambda e: e._id) # might not work in the future if we have multiple merges in one dataflow?
- .process(FlinkCollectOperator())
- .name("Collect")
- )
- """Stream that ingests events with an `cascade.dataflow.dataflow.CollectNode` target"""
+ # MOVED TO END OF OP STREAMS!
+ # self.merge_op_stream = (
+ # event_stream.filter(lambda e: isinstance(e.target, CollectNode))
+ # .key_by(lambda e: e._id) # might not work in the future if we have multiple merges in one dataflow?
+ # .process(FlinkCollectOperator())
+ # .name("Collect")
+ # )
+ # """Stream that ingests events with an `cascade.dataflow.dataflow.CollectNode` target"""
self.stateless_op_streams = []
self.stateful_op_streams = []
@@ -455,9 +484,11 @@ def add_operator(self, op: StatefulOperator):
flink_op = FlinkOperator(op)
op_stream = (
- self.stateful_op_stream.filter(lambda e: e.target.operator.entity == flink_op.operator.entity)
- .key_by(lambda e: e.key_stack[-1])
+ self.stateful_op_stream.filter(lambda e: isinstance(e.target, OpNode) and e.target.entity == flink_op.operator.entity)
+ # .map(lambda x: debug(x, msg=f"filtered op: {op.entity}"))
+ .key_by(lambda e: e.variable_map[e.target.read_key_from])
.process(flink_op)
+ # .map(lambda x: debug(x, msg=f"processed op: {op.entity}"))
.name("STATEFUL OP: " + flink_op.operator.entity.__name__)
)
self.stateful_op_streams.append(op_stream)
@@ -468,7 +499,7 @@ def add_stateless_operator(self, op: StatelessOperator):
op_stream = (
self.stateless_op_stream
- .filter(lambda e: e.target.operator.dataflow.name == flink_op.operator.dataflow.name)
+ .filter(lambda e: isinstance(e.target, StatelessOpNode) and e.target.operator.dataflow.name == flink_op.operator.dataflow.name)
.process(flink_op)
.name("STATELESS DATAFLOW: " + flink_op.operator.dataflow.name)
)
@@ -495,8 +526,21 @@ def run(self, run_async=False, output: Literal["collect", "kafka", "stdout"]="ka
logger.debug("FlinkRuntime merging operator streams...")
# Combine all the operator streams
- operator_streams = self.merge_op_stream.union(*self.stateful_op_streams).union(*self.stateless_op_streams)
+ # operator_streams = self.merge_op_stream.union(*self.stateful_op_streams[1:], *self.stateless_op_streams)#.map(lambda x: debug(x, msg="combined ops"))
+ s1 = self.stateful_op_streams[0]
+ rest = self.stateful_op_streams[1:]
+ operator_streams = s1.union(*rest, *self.stateless_op_streams)#.map(lambda x: debug(x, msg="combined ops"))
+
+ merge_op_stream = (
+ operator_streams.filter(lambda e: isinstance(e, Event) and isinstance(e.target, CollectNode))
+ .key_by(lambda e: e._id) # might not work in the future if we have multiple merges in one dataflow?
+ .process(FlinkCollectOperator())
+ .name("Collect")
+ )
+ """Stream that ingests events with an `cascade.dataflow.dataflow.CollectNode` target"""
+
+ """
# Add filtering for nodes with a `Filter` target
full_stream_filtered = (
operator_streams
@@ -508,18 +552,26 @@ def run(self, run_async=False, output: Literal["collect", "kafka", "stdout"]="ka
.filter(lambda e: not (isinstance(e, Event) and isinstance(e.target, Filter)))
)
ds = full_stream_filtered.union(full_stream_unfiltered)
+ """
+ # union with EventResults or Events that don't have a CollectNode target
+ ds = merge_op_stream.union(operator_streams.filter(lambda e: not (isinstance(e, Event) and isinstance(e.target, CollectNode))))
# Output the stream
+ results = (
+ ds
+ .filter(lambda e: isinstance(e, EventResult))
+ .map(lambda e: timestamp_result(e))
+ )
if output == "collect":
- ds_external = ds.filter(lambda e: isinstance(e, EventResult)).execute_and_collect()
+ ds_external = results.execute_and_collect()
elif output == "stdout":
- ds_external = ds.filter(lambda e: isinstance(e, EventResult)).print()
+ ds_external = results.print()
elif output == "kafka":
- ds_external = ds.filter(lambda e: isinstance(e, EventResult)).sink_to(self.kafka_external_sink).name("EXTERNAL KAFKA SINK")
+ ds_external = results.sink_to(self.kafka_external_sink).name("EXTERNAL KAFKA SINK")
else:
raise ValueError(f"Invalid output: {output}")
- ds_internal = ds.filter(lambda e: isinstance(e, Event)).sink_to(self.kafka_internal_sink).name("INTERNAL KAFKA SINK")
+ ds_internal = ds.filter(lambda e: isinstance(e, Event)).map(lambda e: timestamp_event(e)).sink_to(self.kafka_internal_sink).name("INTERNAL KAFKA SINK")
if run_async:
logger.debug("FlinkRuntime starting (async)")
@@ -583,7 +635,20 @@ def consume_results(self):
def flush(self):
self.producer.flush()
- def send(self, event: Event, flush=False) -> int:
+ def send(self, event: Union[Event, list[Event]], flush=False) -> int:
+ if isinstance(event, list):
+ for e in event:
+ id = self._send(e)
+ else:
+ id = self._send(event)
+
+ if flush:
+ self.producer.flush()
+
+ return id
+
+
+ def _send(self, event: Event) -> int:
"""Send an event to the Kafka source and block until an EventResult is recieved.
:param event: The event to send.
@@ -602,8 +667,6 @@ def set_ts(ts):
self._futures[event._id]["sent_t"] = ts
self.producer.produce(self.input_topic, value=pickle.dumps(event), on_delivery=lambda err, msg: set_ts(msg.timestamp()))
- if flush:
- self.producer.flush()
return event._id
def close(self):
diff --git a/src/cascade/runtime/python_runtime.py b/src/cascade/runtime/python_runtime.py
index cf936f3..a955e9c 100644
--- a/src/cascade/runtime/python_runtime.py
+++ b/src/cascade/runtime/python_runtime.py
@@ -1,7 +1,8 @@
from logging import Filter
import threading
+from typing import Type
from cascade.dataflow.operator import StatefulOperator, StatelessOperator
-from cascade.dataflow.dataflow import CollectNode, Event, EventResult, InitClass, InvokeMethod, OpNode, SelectAllNode
+from cascade.dataflow.dataflow import CollectNode, Event, EventResult, InitClass, InvokeMethod, OpNode, SelectAllNode, StatelessOpNode
from queue import Empty, Queue
class PythonStatefulOperator():
@@ -11,17 +12,15 @@ def __init__(self, operator: StatefulOperator):
def process(self, event: Event):
assert(isinstance(event.target, OpNode))
- assert(isinstance(event.target.operator, StatefulOperator))
- assert(event.target.operator.entity == self.operator.entity)
- key_stack = event.key_stack
- key = key_stack[-1]
+ assert(event.target.entity == self.operator.entity)
- print(f"PythonStatefulOperator: {event}")
+ key = event.variable_map[event.target.read_key_from]
+
+ print(f"PythonStatefulOperator[{self.operator.entity.__name__}[{key}]]: {event}")
if isinstance(event.target.method_type, InitClass):
result = self.operator.handle_init_class(*event.variable_map.values())
self.states[key] = result
- key_stack.pop()
elif isinstance(event.target.method_type, InvokeMethod):
state = self.states[key]
@@ -29,7 +28,6 @@ def process(self, event: Event):
event.target.method_type,
variable_map=event.variable_map,
state=state,
- key_stack=key_stack
)
self.states[key] = state
@@ -39,7 +37,7 @@ def process(self, event: Event):
if event.target.assign_result_to is not None:
event.variable_map[event.target.assign_result_to] = result
- new_events = event.propogate(key_stack, result)
+ new_events = event.propogate(result)
if isinstance(new_events, EventResult):
yield new_events
else:
@@ -50,17 +48,14 @@ def __init__(self, operator: StatelessOperator):
self.operator = operator
def process(self, event: Event):
- assert(isinstance(event.target, OpNode))
- assert(isinstance(event.target.operator, StatelessOperator))
-
- key_stack = event.key_stack
+ assert(isinstance(event.target, StatelessOpNode))
+ print(f"PythonStatelessOperator[{self.operator.dataflow.name}]: {event}")
if isinstance(event.target.method_type, InvokeMethod):
result = self.operator.handle_invoke_method(
event.target.method_type,
variable_map=event.variable_map,
- key_stack=key_stack
)
else:
raise Exception(f"A StatelessOperator cannot compute event type: {event.target.method_type}")
@@ -68,7 +63,7 @@ def process(self, event: Event):
if event.target.assign_result_to is not None:
event.variable_map[event.target.assign_result_to] = result
- new_events = event.propogate(key_stack, result)
+ new_events = event.propogate(result)
if isinstance(new_events, EventResult):
yield new_events
else:
@@ -81,8 +76,8 @@ def __init__(self):
self.events = Queue()
self.results = Queue()
self.running = False
- self.statefuloperators: dict[StatefulOperator, PythonStatefulOperator] = {}
- self.statelessoperators: dict[StatelessOperator, PythonStatelessOperator] = {}
+ self.statefuloperators: dict[Type, PythonStatefulOperator] = {}
+ self.statelessoperators: dict[str, PythonStatelessOperator] = {}
def init(self):
pass
@@ -91,10 +86,9 @@ def _consume_events(self):
self.running = True
def consume_event(event: Event):
if isinstance(event.target, OpNode):
- if isinstance(event.target.operator, StatefulOperator):
- yield from self.statefuloperators[event.target.operator].process(event)
- elif isinstance(event.target.operator, StatelessOperator):
- yield from self.statelessoperators[event.target.operator].process(event)
+ yield from self.statefuloperators[event.target.entity].process(event)
+ elif isinstance(event.target, StatelessOpNode):
+ yield from self.statelessoperators[event.target.operator.dataflow.name].process(event)
elif isinstance(event.target, SelectAllNode):
raise NotImplementedError()
@@ -121,11 +115,11 @@ def consume_event(event: Event):
def add_operator(self, op: StatefulOperator):
"""Add a `StatefulOperator` to the datastream."""
- self.statefuloperators[op] = PythonStatefulOperator(op)
+ self.statefuloperators[op.entity] = PythonStatefulOperator(op)
def add_stateless_operator(self, op: StatelessOperator):
"""Add a `StatelessOperator` to the datastream."""
- self.statelessoperators[op] = PythonStatelessOperator(op)
+ self.statelessoperators[op.dataflow.name] = PythonStatelessOperator(op)
def send(self, event: Event, flush=None):
self.events.put(event)
diff --git a/test_programs/expected/checkout_item.py b/test_programs/expected/checkout_item.py
index fd256bf..75a32fa 100644
--- a/test_programs/expected/checkout_item.py
+++ b/test_programs/expected/checkout_item.py
@@ -1,29 +1,27 @@
from typing import Any
-# from ..target.checkout_item import User, Item
-# from cascade.dataflow.dataflow import DataFlow, OpNode, InvokeMethod, Edge
-def buy_item_0_compiled(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.append(variable_map['item_key'])
+from cascade.dataflow.dataflow import DataFlow, Edge, InvokeMethod, OpNode
+from test_programs.target.checkout_item import User, Item
+
+def buy_item_0_compiled(variable_map: dict[str, Any], state: User) -> Any:
return None
-def buy_item_1_compiled(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.pop()
+def buy_item_1_compiled(variable_map: dict[str, Any], state: User) -> Any:
item_price_0 = variable_map['item_price_0']
state.balance -= item_price_0
return state.balance >= 0
-def get_price_0_compiled(variable_map: dict[str, Any], state: Item, key_stack: list[str]) -> Any:
- key_stack.pop()
+def get_price_0_compiled(variable_map: dict[str, Any], state: Item) -> Any:
return state.price
def user_buy_item_df():
df = DataFlow("user.buy_item")
- n0 = OpNode(User, InvokeMethod("buy_item_0"))
- n1 = OpNode(Item, InvokeMethod("get_price"), assign_result_to="item_price")
- n2 = OpNode(User, InvokeMethod("buy_item_1"))
+ n0 = OpNode(User, InvokeMethod("buy_item_0"), read_key_from="user_key")
+ n1 = OpNode(Item, InvokeMethod("get_price"), assign_result_to="item_price", read_key_from="item_key")
+ n2 = OpNode(User, InvokeMethod("buy_item_1"), read_key_from="user_key")
df.add_edge(Edge(n0, n1))
df.add_edge(Edge(n1, n2))
df.entry = n0
diff --git a/test_programs/expected/checkout_two_items.py b/test_programs/expected/checkout_two_items.py
index 2081cd3..9849ad5 100644
--- a/test_programs/expected/checkout_two_items.py
+++ b/test_programs/expected/checkout_two_items.py
@@ -1,30 +1,22 @@
from typing import Any
-
+from cascade.dataflow.dataflow import CollectNode, CollectTarget, DataFlow, OpNode, InvokeMethod, Edge
from cascade.dataflow.operator import StatefulOperator
-from ..target.checkout_two_items import User, Item
-from cascade.dataflow.dataflow import DataFlow, OpNode, InvokeMethod, Edge, CollectNode, CollectTarget
+from test_programs.target.checkout_two_items import User, Item
-def buy_two_items_0_compiled(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.append(
- [variable_map["item1_key"], variable_map["item2_key"]]
- )
+def buy_two_items_0_compiled(variable_map: dict[str, Any], state: User) -> Any:
return None
-def buy_two_items_1_compiled(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.pop()
- item_price_1_0 = variable_map['item_price_1']
- item_price_2_0 = variable_map['item_price_2']
+def buy_two_items_1_compiled(variable_map: dict[str, Any], state: User) -> Any:
+ item_price_1_0 = variable_map['item_price_1_0']
+ item_price_2_0 = variable_map['item_price_2_0']
total_price_0 = item_price_1_0 + item_price_2_0
state.balance -= total_price_0
return state.balance >= 0
-
-def get_price_0_compiled(variable_map: dict[str, Any], state: Item, key_stack: list[str]) -> Any:
- key_stack.pop()
+def get_price_0_compiled(variable_map: dict[str, Any], state: Item) -> Any:
return state.price
-# An operator is defined by the underlying class and the functions that can be called
user_op = StatefulOperator(
User,
{
@@ -39,18 +31,20 @@ def get_price_0_compiled(variable_map: dict[str, Any], state: Item, key_stack: l
def user_buy_two_items_df():
df = DataFlow("user.buy_2_items")
- n0 = OpNode(user_op, InvokeMethod("buy_2_items_0"))
+ n0 = OpNode(User, InvokeMethod("buy_2_items_0"), read_key_from="user_key")
n1 = OpNode(
- item_op,
+ Item,
InvokeMethod("get_price"),
assign_result_to="item_price_1",
+ read_key_from="item1_key"
)
n2 = OpNode(
- item_op,
+ Item,
InvokeMethod("get_price"),
assign_result_to="item_price_2",
+ read_key_from="item1_key"
)
- n3 = OpNode(user_op, InvokeMethod("buy_2_items_1"))
+ n3 = OpNode(User, InvokeMethod("buy_2_items_1"), read_key_from="user_key")
df.add_edge(Edge(n0, n1))
df.add_edge(Edge(n0, n2))
df.add_edge(Edge(n1, n2))
@@ -62,21 +56,23 @@ def user_buy_two_items_df():
# For future optimizations (not used)
def user_buy_two_items_df_parallelized():
df = DataFlow("user.buy_2_items")
- n0 = OpNode(user_op, InvokeMethod("buy_2_items_0"))
+ n0 = OpNode(User, InvokeMethod("buy_2_items_0"), read_key_from="user_key")
n3 = CollectNode(assign_result_to="item_prices", read_results_from="item_price")
n1 = OpNode(
- item_op,
+ Item,
InvokeMethod("get_price"),
assign_result_to="item_price",
- collect_target=CollectTarget(n3, 2, 0)
+ collect_target=CollectTarget(n3, 2, 0),
+ read_key_from="item1_key"
)
n2 = OpNode(
- item_op,
+ Item,
InvokeMethod("get_price"),
assign_result_to="item_price",
- collect_target=CollectTarget(n3, 2, 1)
+ collect_target=CollectTarget(n3, 2, 1),
+ read_key_from="item1_key"
)
- n4 = OpNode(user_op, InvokeMethod("buy_2_items_1"))
+ n4 = OpNode(User, InvokeMethod("buy_2_items_1"), read_key_from="user_key")
df.add_edge(Edge(n0, n1))
df.add_edge(Edge(n0, n2))
df.add_edge(Edge(n1, n3))
diff --git a/test_programs/expected/deathstar_recommendation.py b/test_programs/expected/deathstar_recommendation.py
index 436aa5d..8a8a727 100644
--- a/test_programs/expected/deathstar_recommendation.py
+++ b/test_programs/expected/deathstar_recommendation.py
@@ -1,53 +1,45 @@
from typing import Any, Literal
-from cascade.dataflow.dataflow import CollectNode, DataFlow, Edge, InvokeMethod, OpNode, SelectAllNode
+from cascade.dataflow.dataflow import CollectNode, DataFlow, Edge, InvokeMethod, OpNode, SelectAllNode, StatelessOpNode
from cascade.dataflow.operator import StatelessOperator
-def get_recs_if_cond(variable_map: dict[str, Any], key_stack: list[str]):
+def get_recs_if_cond(variable_map: dict[str, Any]):
return variable_map["requirement"] == "distance"
# list comprehension entry
-def get_recs_if_body_0(variable_map: dict[str, Any], key_stack: list[str]):
- hotel_key = key_stack[-1]
- # The body will need the hotel key (actually, couldn't we just take the top of the key stack again?)
- variable_map["hotel_key"] = hotel_key
- # The next node (Hotel.get_geo) will need the hotel key
- key_stack.append(hotel_key)
+def get_recs_if_body_0(variable_map: dict[str, Any]):
+ pass
# list comprehension body
-def get_recs_if_body_1(variable_map: dict[str, Any], key_stack: list[str]):
- hotel_geo: Geo = variable_map["hotel_geo"]
+def get_recs_if_body_1(variable_map: dict[str, Any]):
+ hotel_geo = variable_map["hotel_geo"]
lat, lon = variable_map["lat"], variable_map["lon"]
dist = hotel_geo.distance_km(lat, lon)
return (dist, variable_map["hotel_key"])
# after list comprehension
-def get_recs_if_body_2(variable_map: dict[str, Any], key_stack: list[str]):
+def get_recs_if_body_2(variable_map: dict[str, Any]):
distances = variable_map["distances"]
min_dist = min(distances, key=lambda x: x[0])[0]
variable_map["res"] = [hotel for dist, hotel in distances if dist == min_dist]
-def get_recs_elif_cond(variable_map: dict[str, Any], key_stack: list[str]):
+def get_recs_elif_cond(variable_map: dict[str, Any]):
return variable_map["requirement"] == "price"
# list comprehension entry
-def get_recs_elif_body_0(variable_map: dict[str, Any], key_stack: list[str]):
- hotel_key = key_stack[-1]
- # The body will need the hotel key (actually, couldn't we just take the top of the key stack again?)
- variable_map["hotel_key"] = hotel_key
- # The next node (Hotel.get_geo) will need the hotel key
- key_stack.append(hotel_key)
+def get_recs_elif_body_0(variable_map: dict[str, Any]):
+ pass
# list comprehension body
-def get_recs_elif_body_1(variable_map: dict[str, Any], key_stack: list[str]):
+def get_recs_elif_body_1(variable_map: dict[str, Any]):
return (variable_map["hotel_price"], variable_map["hotel_key"])
# after list comprehension
-def get_recs_elif_body_2(variable_map: dict[str, Any], key_stack: list[str]):
+def get_recs_elif_body_2(variable_map: dict[str, Any]):
prices = variable_map["prices"]
min_price = min(prices, key=lambda x: x[0])[0]
variable_map["res"] = [hotel for price, hotel in prices if price == min_price]
@@ -56,7 +48,7 @@ def get_recs_elif_body_2(variable_map: dict[str, Any], key_stack: list[str]):
# a future optimization might instead duplicate this piece of code over the two
# branches, in order to reduce the number of splits by one
-def get_recs_final(variable_map: dict[str, Any], key_stack: list[str]):
+def get_recs_final(variable_map: dict[str, Any]):
return variable_map["res"]
@@ -74,24 +66,24 @@ def get_recs_final(variable_map: dict[str, Any], key_stack: list[str]):
def get_recommendations_df():
df = DataFlow("get_recommendations")
- n1 = OpNode(recommend_op, InvokeMethod("get_recs_if_cond"), is_conditional=True)
- n2 = OpNode(recommend_op, InvokeMethod("get_recs_if_body_0"))
- n3 = OpNode(hotel_op, InvokeMethod("get_geo"), assign_result_to="hotel_geo")
- n4 = OpNode(recommend_op, InvokeMethod("get_recs_if_body_1"), assign_result_to="distance")
+ n1 = StatelessOpNode(recommend_op, InvokeMethod("get_recs_if_cond"), is_conditional=True)
+ n2 = StatelessOpNode(recommend_op, InvokeMethod("get_recs_if_body_0"))
+ n3 = OpNode(Hotel, InvokeMethod("get_geo"), assign_result_to="hotel_geo", read_key_from="hotel_key")
+ n4 = StatelessOpNode(recommend_op, InvokeMethod("get_recs_if_body_1"), assign_result_to="distance")
n5 = CollectNode("distances", "distance")
- n6 = OpNode(recommend_op, InvokeMethod("get_recs_if_body_2"))
- ns1 = SelectAllNode(Hotel, n5)
+ n6 = StatelessOpNode(recommend_op, InvokeMethod("get_recs_if_body_2"))
+ ns1 = SelectAllNode(Hotel, n5, assign_key_to="hotel_key")
- n7 = OpNode(recommend_op, InvokeMethod("get_recs_elif_cond"), is_conditional=True)
- n8 = OpNode(recommend_op, InvokeMethod("get_recs_elif_body_0"))
- n9 = OpNode(hotel_op, InvokeMethod("get_price"), assign_result_to="hotel_price")
- n10 = OpNode(recommend_op, InvokeMethod("get_recs_elif_body_1"), assign_result_to="price")
+ n7 = StatelessOpNode(recommend_op, InvokeMethod("get_recs_elif_cond"), is_conditional=True)
+ n8 = StatelessOpNode(recommend_op, InvokeMethod("get_recs_elif_body_0"))
+ n9 = OpNode(Hotel, InvokeMethod("get_price"), assign_result_to="hotel_price", read_key_from="hotel_key")
+ n10 = StatelessOpNode(recommend_op, InvokeMethod("get_recs_elif_body_1"), assign_result_to="price")
n11 = CollectNode("prices", "price")
- n12 = OpNode(recommend_op, InvokeMethod("get_recs_elif_body_2"))
- ns2 = SelectAllNode(Hotel, n11)
+ n12 = StatelessOpNode(recommend_op, InvokeMethod("get_recs_elif_body_2"))
+ ns2 = SelectAllNode(Hotel, n11, assign_key_to="hotel_key")
- n13 = OpNode(recommend_op, InvokeMethod("get_recs_final"))
+ n13 = StatelessOpNode(recommend_op, InvokeMethod("get_recs_final"))
df.add_edge(Edge(n1, ns1, if_conditional=True))
df.add_edge(Edge(n1, n7, if_conditional=False))
diff --git a/test_programs/expected/deathstar_search.py b/test_programs/expected/deathstar_search.py
index 06cbec0..cd20593 100644
--- a/test_programs/expected/deathstar_search.py
+++ b/test_programs/expected/deathstar_search.py
@@ -1,24 +1,15 @@
from typing import Any
-from cascade.dataflow.dataflow import CollectNode, DataFlow, Edge, InvokeMethod, OpNode, SelectAllNode
+from cascade.dataflow.dataflow import CollectNode, DataFlow, Edge, InvokeMethod, OpNode, SelectAllNode, StatelessOpNode
from cascade.dataflow.operator import StatelessOperator
-
# predicate 1
-def search_nearby_compiled_0(variable_map: dict[str, Any], key_stack: list[str]):
- # We assume that the top of the key stack is the hotel key.
- # This assumption holds if the node before this one is a correctly
- # configure SelectAllNode.
-
- hotel_key = key_stack[-1]
- # The body will need the hotel key (actually, couldn't we just take the top of the key stack again?)
- variable_map["hotel_key"] = hotel_key
- # The next node (Hotel.get_geo) will need the hotel key
- key_stack.append(hotel_key)
+def search_nearby_compiled_0(variable_map: dict[str, Any]):
+ pass
# predicate 2
-def search_nearby_compiled_1(variable_map: dict[str, Any], key_stack: list[str]):
- hotel_geo = variable_map["hotel_geo"]
+def search_nearby_compiled_1(variable_map: dict[str, Any]):
+ hotel_geo: Geo = variable_map["hotel_geo"]
lat, lon = variable_map["lat"], variable_map["lon"]
dist = hotel_geo.distance_km(lat, lon)
variable_map["dist"] = dist
@@ -26,11 +17,11 @@ def search_nearby_compiled_1(variable_map: dict[str, Any], key_stack: list[str])
# body
-def search_nearby_compiled_2(variable_map: dict[str, Any], key_stack: list[str]):
+def search_nearby_compiled_2(variable_map: dict[str, Any]):
return (variable_map["dist"], variable_map["hotel_key"])
# next line
-def search_nearby_compiled_3(variable_map: dict[str, Any], key_stack: list[str]):
+def search_nearby_compiled_3(variable_map: dict[str, Any]):
distances = variable_map["distances"]
hotels = [hotel for dist, hotel in sorted(distances)[:5]]
return hotels
@@ -45,14 +36,14 @@ def search_nearby_compiled_3(variable_map: dict[str, Any], key_stack: list[str])
def search_nearby_df():
df = DataFlow("search_nearby")
- n1 = OpNode(search_op, InvokeMethod("search_nearby_compiled_0"))
- n2 = OpNode(hotel_op, InvokeMethod("get_geo"), assign_result_to="hotel_geo")
- n3 = OpNode(search_op, InvokeMethod("search_nearby_compiled_1"), is_conditional=True)
- n4 = OpNode(search_op, InvokeMethod("search_nearby_compiled_2"), assign_result_to="search_body")
+ n1 = StatelessOpNode(search_op, InvokeMethod("search_nearby_compiled_0"))
+ n2 = OpNode(Hotel, InvokeMethod("get_geo"), assign_result_to="hotel_geo", read_key_from="hotel_key")
+ n3 = StatelessOpNode(search_op, InvokeMethod("search_nearby_compiled_1"), is_conditional=True)
+ n4 = StatelessOpNode(search_op, InvokeMethod("search_nearby_compiled_2"), assign_result_to="search_body")
n5 = CollectNode("distances", "search_body")
- n0 = SelectAllNode(Hotel, n5)
+ n0 = SelectAllNode(Hotel, n5, assign_key_to="hotel_key")
- n6 = OpNode(search_op, InvokeMethod("search_nearby_compiled_3"))
+ n6 = StatelessOpNode(search_op, InvokeMethod("search_nearby_compiled_3"))
df.add_edge(Edge(n0, n1))
df.add_edge(Edge(n1, n2))
diff --git a/test_programs/expected/deathstar_user.py b/test_programs/expected/deathstar_user.py
index 5aea434..64985ea 100644
--- a/test_programs/expected/deathstar_user.py
+++ b/test_programs/expected/deathstar_user.py
@@ -2,24 +2,21 @@
from cascade.dataflow.dataflow import DataFlow, Edge, InvokeMethod, OpNode
from cascade.dataflow.operator import StatefulOperator
-def order_compiled_entry_0(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.append(variable_map["hotel"])
+def order_compiled_entry_0(variable_map: dict[str, Any], state: User) -> Any:
+ pass
-def order_compiled_entry_1(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.append(variable_map["flight"])
+def order_compiled_entry_1(variable_map: dict[str, Any], state: User) -> Any:
+ pass
-def order_compiled_if_cond(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
+def order_compiled_if_cond(variable_map: dict[str, Any], state: User) -> Any:
return variable_map["hotel_reserve"] and variable_map["flight_reserve"]
-def order_compiled_if_body(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.pop()
+def order_compiled_if_body(variable_map: dict[str, Any], state: User) -> Any:
return True
-def order_compiled_else_body(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.pop()
+def order_compiled_else_body(variable_map: dict[str, Any], state: User) -> Any:
return False
-
user_op = StatefulOperator(
User,
{
@@ -29,7 +26,7 @@ def order_compiled_else_body(variable_map: dict[str, Any], state: User, key_stac
"order_compiled_if_body": order_compiled_if_body,
"order_compiled_else_body": order_compiled_else_body
},
- {} # dataflows (filled later)
+ {}
)
# For now, the dataflow will be serial instead of parallel (calling hotel, then
@@ -39,13 +36,13 @@ def order_compiled_else_body(variable_map: dict[str, Any], state: User, key_stac
# before the first entity call).
def user_order_df():
df = DataFlow("user_order")
- n0 = OpNode(user_op, InvokeMethod("order_compiled_entry_0"))
- n1 = OpNode(hotel_op, InvokeMethod("reserve"), assign_result_to="hotel_reserve")
- n2 = OpNode(user_op, InvokeMethod("order_compiled_entry_1"))
- n3 = OpNode(flight_op, InvokeMethod("reserve"), assign_result_to="flight_reserve")
- n4 = OpNode(user_op, InvokeMethod("order_compiled_if_cond"), is_conditional=True)
- n5 = OpNode(user_op, InvokeMethod("order_compiled_if_body"))
- n6 = OpNode(user_op, InvokeMethod("order_compiled_else_body"))
+ n0 = OpNode(User, InvokeMethod("order_compiled_entry_0"), read_key_from="user_key")
+ n1 = OpNode(Hotel, InvokeMethod("reserve"), assign_result_to="hotel_reserve", read_key_from="hotel_key")
+ n2 = OpNode(User, InvokeMethod("order_compiled_entry_1"), read_key_from="user_key")
+ n3 = OpNode(Flight, InvokeMethod("reserve"), assign_result_to="flight_reserve", read_key_from="flight_key")
+ n4 = OpNode(User, InvokeMethod("order_compiled_if_cond"), is_conditional=True, read_key_from="user_key")
+ n5 = OpNode(User, InvokeMethod("order_compiled_if_body"), read_key_from="user_key")
+ n6 = OpNode(User, InvokeMethod("order_compiled_else_body"), read_key_from="user_key")
df.add_edge(Edge(n0, n1))
df.add_edge(Edge(n1, n2))
diff --git a/tests/integration/flink-runtime/common.py b/tests/integration/flink-runtime/common.py
index 49a0ef3..a7d7af6 100644
--- a/tests/integration/flink-runtime/common.py
+++ b/tests/integration/flink-runtime/common.py
@@ -39,38 +39,28 @@ def get_price(self) -> int:
def __repr__(self):
return f"Item(key='{self.key}', price={self.price})"
-def update_balance_compiled(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.pop() # final function
+def update_balance_compiled(variable_map: dict[str, Any], state: User) -> Any:
state.balance += variable_map["amount"]
return state.balance >= 0
-def get_balance_compiled(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.pop() # final function
+def get_balance_compiled(variable_map: dict[str, Any], state: User) -> Any:
return state.balance
-def get_price_compiled(variable_map: dict[str, Any], state: Item, key_stack: list[str]) -> Any:
- key_stack.pop() # final function
+def get_price_compiled(variable_map: dict[str, Any], state: Item) -> Any:
return state.price
-# Items (or other operators) are passed by key always
-def buy_item_0_compiled(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.append(variable_map["item_key"])
+def buy_item_0_compiled(variable_map: dict[str, Any], state: User) -> Any:
return None
-def buy_item_1_compiled(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.pop()
+def buy_item_1_compiled(variable_map: dict[str, Any], state: User) -> Any:
state.balance = state.balance - variable_map["item_price"]
return state.balance >= 0
-def buy_2_items_0_compiled(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.append(
- [variable_map["item1_key"], variable_map["item2_key"]]
- )
+def buy_2_items_0_compiled(variable_map: dict[str, Any], state: User) -> Any:
return None
-def buy_2_items_1_compiled(variable_map: dict[str, Any], state: User, key_stack: list[str]) -> Any:
- key_stack.pop()
+def buy_2_items_1_compiled(variable_map: dict[str, Any], state: User) -> Any:
state.balance -= variable_map["item_prices"][0] + variable_map["item_prices"][1]
return state.balance >= 0
@@ -94,9 +84,12 @@ def buy_2_items_1_compiled(variable_map: dict[str, Any], state: User, key_stack:
def user_buy_item_df():
df = DataFlow("user.buy_item")
- n0 = OpNode(user_op, InvokeMethod("buy_item_0"))
- n1 = OpNode(item_op, InvokeMethod("get_price"), assign_result_to="item_price")
- n2 = OpNode(user_op, InvokeMethod("buy_item_1"))
+ n0 = OpNode(User, InvokeMethod("buy_item_0"), read_key_from="user_key")
+ n1 = OpNode(Item,
+ InvokeMethod("get_price"),
+ assign_result_to="item_price",
+ read_key_from="item_key")
+ n2 = OpNode(User, InvokeMethod("buy_item_1"), read_key_from="user_key")
df.add_edge(Edge(n0, n1))
df.add_edge(Edge(n1, n2))
df.entry = n0
@@ -104,21 +97,23 @@ def user_buy_item_df():
def user_buy_2_items_df():
df = DataFlow("user.buy_2_items")
- n0 = OpNode(user_op, InvokeMethod("buy_2_items_0"))
+ n0 = OpNode(User, InvokeMethod("buy_2_items_0"), read_key_from="user_key")
n3 = CollectNode(assign_result_to="item_prices", read_results_from="item_price")
n1 = OpNode(
- item_op,
+ Item,
InvokeMethod("get_price"),
assign_result_to="item_price",
- collect_target=CollectTarget(n3, 2, 0)
+ collect_target=CollectTarget(n3, 2, 0),
+ read_key_from="item1_key"
)
n2 = OpNode(
- item_op,
+ Item,
InvokeMethod("get_price"),
assign_result_to="item_price",
- collect_target=CollectTarget(n3, 2, 1)
+ collect_target=CollectTarget(n3, 2, 1),
+ read_key_from="item2_key"
)
- n4 = OpNode(user_op, InvokeMethod("buy_2_items_1"))
+ n4 = OpNode(User, InvokeMethod("buy_2_items_1"), read_key_from="user_key")
df.add_edge(Edge(n0, n1))
df.add_edge(Edge(n0, n2))
df.add_edge(Edge(n1, n3))
diff --git a/tests/integration/flink-runtime/test_merge_operator.py b/tests/integration/flink-runtime/test_collect_operator.py
similarity index 68%
rename from tests/integration/flink-runtime/test_merge_operator.py
rename to tests/integration/flink-runtime/test_collect_operator.py
index d136d99..d14418f 100644
--- a/tests/integration/flink-runtime/test_merge_operator.py
+++ b/tests/integration/flink-runtime/test_collect_operator.py
@@ -1,71 +1,71 @@
-"""A test script for dataflows with merge operators"""
-
-from pyflink.datastream.data_stream import CloseableIterator
-from common import Item, User, item_op, user_op
-from cascade.dataflow.dataflow import Event, EventResult, InitClass, InvokeMethod, OpNode
-from cascade.runtime.flink_runtime import FlinkOperator, FlinkRuntime
-import pytest
-
-@pytest.mark.integration
-def test_merge_operator():
- runtime = FlinkRuntime("test_merge_operator")
- runtime.init()
- runtime.add_operator(FlinkOperator(item_op))
- runtime.add_operator(FlinkOperator(user_op))
-
-
- # Create a User object
- foo_user = User("foo", 100)
- init_user_node = OpNode(user_op, InitClass())
- event = Event(init_user_node, ["foo"], {"key": "foo", "balance": 100}, None)
- runtime.send(event)
-
- # Create an Item object
- fork_item = Item("fork", 5)
- init_item_node = OpNode(item_op, InitClass())
- event = Event(init_item_node, ["fork"], {"key": "fork", "price": 5}, None)
- runtime.send(event)
-
- # Create another Item
- spoon_item = Item("spoon", 3)
- event = Event(init_item_node, ["spoon"], {"key": "spoon", "price": 3}, None)
- runtime.send(event, flush=True)
-
- collected_iterator: CloseableIterator = runtime.run(run_async=True, collect=True)
- records = []
-
- def wait_for_event_id(id: int) -> EventResult:
- for record in collected_iterator:
- records.append(record)
- print(f"Collected record: {record}")
- if record.event_id == id:
- return record
-
- # Make sure the user & items are initialised
- wait_for_event_id(event._id)
-
- # Have the User object buy the item
- foo_user.buy_2_items(fork_item, spoon_item)
- df = user_op.dataflows["buy_2_items"]
-
- # User with key "foo" buys item with key "fork"
- user_buys_cutlery = Event(df.entry, ["foo"], {"item1_key": "fork", "item2_key": "spoon"}, df)
- runtime.send(user_buys_cutlery, flush=True)
-
-
- # Check that we were able to buy the fork
- buy_fork_result = wait_for_event_id(user_buys_cutlery._id)
- assert buy_fork_result.result == True
-
- # Send an event to check if the balance was updated
- user_get_balance_node = OpNode(user_op, InvokeMethod("get_balance"))
- user_get_balance = Event(user_get_balance_node, ["foo"], {}, None)
- runtime.send(user_get_balance, flush=True)
-
- # See that the user's balance has gone down
- get_balance = wait_for_event_id(user_get_balance._id)
- assert get_balance.result == 92
-
- collected_iterator.close()
-
+"""A test script for dataflows with merge operators"""
+
+from pyflink.datastream.data_stream import CloseableIterator
+from common import Item, User, item_op, user_op
+from cascade.dataflow.dataflow import Event, EventResult, InitClass, InvokeMethod, OpNode
+from cascade.runtime.flink_runtime import FlinkOperator, FlinkRuntime
+import pytest
+
+@pytest.mark.integration
+def test_merge_operator():
+ runtime = FlinkRuntime("test_collect_operator")
+ runtime.init()
+ runtime.add_operator(item_op)
+ runtime.add_operator(user_op)
+
+
+ # Create a User object
+ foo_user = User("foo", 100)
+ init_user_node = OpNode(User, InitClass(), read_key_from="key")
+ event = Event(init_user_node, {"key": "foo", "balance": 100}, None)
+ runtime.send(event)
+
+ # Create an Item object
+ fork_item = Item("fork", 5)
+ init_item_node = OpNode(Item, InitClass(), read_key_from="key")
+ event = Event(init_item_node, {"key": "fork", "price": 5}, None)
+ runtime.send(event)
+
+ # Create another Item
+ spoon_item = Item("spoon", 3)
+ event = Event(init_item_node, {"key": "spoon", "price": 3}, None)
+ runtime.send(event, flush=True)
+
+ collected_iterator: CloseableIterator = runtime.run(run_async=True, output="collect")
+ records = []
+
+ def wait_for_event_id(id: int) -> EventResult:
+ for record in collected_iterator:
+ records.append(record)
+ print(f"Collected record: {record}")
+ if record.event_id == id:
+ return record
+
+ # Make sure the user & items are initialised
+ wait_for_event_id(event._id)
+
+ # Have the User object buy the item
+ foo_user.buy_2_items(fork_item, spoon_item)
+ df = user_op.dataflows["buy_2_items"]
+
+ # User with key "foo" buys item with key "fork"
+ user_buys_cutlery = Event(df.entry, {"user_key": "foo", "item1_key": "fork", "item2_key": "spoon"}, df)
+ runtime.send(user_buys_cutlery, flush=True)
+
+
+ # Check that we were able to buy the fork
+ buy_fork_result = wait_for_event_id(user_buys_cutlery._id)
+ assert buy_fork_result.result == True
+
+ # Send an event to check if the balance was updated
+ user_get_balance_node = OpNode(User, InvokeMethod("get_balance"), read_key_from="key")
+ user_get_balance = Event(user_get_balance_node, {"key": "foo"}, None)
+ runtime.send(user_get_balance, flush=True)
+
+ # See that the user's balance has gone down
+ get_balance = wait_for_event_id(user_get_balance._id)
+ assert get_balance.result == 92
+
+ collected_iterator.close()
+
print(records)
\ No newline at end of file
diff --git a/tests/integration/flink-runtime/test_select_all.py b/tests/integration/flink-runtime/test_select_all.py
index f585092..602858d 100644
--- a/tests/integration/flink-runtime/test_select_all.py
+++ b/tests/integration/flink-runtime/test_select_all.py
@@ -1,5 +1,5 @@
"""
-Basically we need a way to search through all state.
+The select all operator is used to fetch all keys for a single entity
"""
import math
import random
@@ -8,10 +8,9 @@
from pyflink.datastream.data_stream import CloseableIterator
-from cascade.dataflow.dataflow import CollectNode, DataFlow, Edge, Event, EventResult, InitClass, InvokeMethod, OpNode, SelectAllNode
+from cascade.dataflow.dataflow import CollectNode, DataFlow, Edge, Event, EventResult, InitClass, InvokeMethod, OpNode, SelectAllNode, StatelessOpNode
from cascade.dataflow.operator import StatefulOperator, StatelessOperator
from cascade.runtime.flink_runtime import FlinkOperator, FlinkRuntime, FlinkStatelessOperator
-from confluent_kafka import Producer
import time
import pytest
@@ -35,13 +34,11 @@ def __repr__(self) -> str:
return f"Hotel({self.name}, {self.loc})"
-def distance_compiled(variable_map: dict[str, Any], state: Hotel, key_stack: list[str]) -> Any:
- key_stack.pop()
+def distance_compiled(variable_map: dict[str, Any], state: Hotel) -> Any:
loc = variable_map["loc"]
return math.sqrt((state.loc.x - loc.x) ** 2 + (state.loc.y - loc.y) ** 2)
-def get_name_compiled(variable_map: dict[str, Any], state: Hotel, key_stack: list[str]) -> Any:
- key_stack.pop()
+def get_name_compiled(variable_map: dict[str, Any], state: Hotel) -> Any:
return state.name
hotel_op = StatefulOperator(Hotel,
@@ -55,24 +52,19 @@ def get_nearby(hotels: list[Hotel], loc: Geo, dist: float):
# We compile just the predicate, the select is implemented using a selectall node
-def get_nearby_predicate_compiled_0(variable_map: dict[str, Any], key_stack: list[str]):
- # the top of the key_stack is already the right key, so in this case we don't need to do anything
- # loc = variable_map["loc"]
- # we need the hotel_key for later. (body_compiled_0)
- variable_map["hotel_key"] = key_stack[-1]
+def get_nearby_predicate_compiled_0(variable_map: dict[str, Any]):
pass
-def get_nearby_predicate_compiled_1(variable_map: dict[str, Any], key_stack: list[str]) -> bool:
+def get_nearby_predicate_compiled_1(variable_map: dict[str, Any]) -> bool:
loc = variable_map["loc"]
dist = variable_map["dist"]
hotel_dist = variable_map["hotel_distance"]
- # key_stack.pop() # shouldn't pop because this function is stateless
return hotel_dist < dist
-def get_nearby_body_compiled_0(variable_map: dict[str, Any], key_stack: list[str]):
- key_stack.append(variable_map["hotel_key"])
+def get_nearby_body_compiled_0(variable_map: dict[str, Any]):
+ pass
-def get_nearby_body_compiled_1(variable_map: dict[str, Any], key_stack: list[str]) -> str:
+def get_nearby_body_compiled_1(variable_map: dict[str, Any]) -> str:
return variable_map["hotel_name"]
get_nearby_op = StatelessOperator({
@@ -85,13 +77,13 @@ def get_nearby_body_compiled_1(variable_map: dict[str, Any], key_stack: list[str
# dataflow for getting all hotels within region
df = DataFlow("get_nearby")
n7 = CollectNode("get_nearby_result", "get_nearby_body")
-n0 = SelectAllNode(Hotel, n7)
-n1 = OpNode(get_nearby_op, InvokeMethod("get_nearby_predicate_compiled_0"))
-n2 = OpNode(hotel_op, InvokeMethod("distance"), assign_result_to="hotel_distance")
-n3 = OpNode(get_nearby_op, InvokeMethod("get_nearby_predicate_compiled_1"), is_conditional=True)
-n4 = OpNode(get_nearby_op, InvokeMethod("get_nearby_body_compiled_0"))
-n5 = OpNode(hotel_op, InvokeMethod("get_name"), assign_result_to="hotel_name")
-n6 = OpNode(get_nearby_op, InvokeMethod("get_nearby_body_compiled_1"), assign_result_to="get_nearby_body")
+n0 = SelectAllNode(Hotel, n7, assign_key_to="hotel_key")
+n1 = StatelessOpNode(get_nearby_op, InvokeMethod("get_nearby_predicate_compiled_0"))
+n2 = OpNode(Hotel, InvokeMethod("distance"), assign_result_to="hotel_distance", read_key_from="hotel_key")
+n3 = StatelessOpNode(get_nearby_op, InvokeMethod("get_nearby_predicate_compiled_1"), is_conditional=True)
+n4 = StatelessOpNode(get_nearby_op, InvokeMethod("get_nearby_body_compiled_0"))
+n5 = OpNode(Hotel, InvokeMethod("get_name"), assign_result_to="hotel_name", read_key_from="hotel_key")
+n6 = StatelessOpNode(get_nearby_op, InvokeMethod("get_nearby_body_compiled_1"), assign_result_to="get_nearby_body")
df.add_edge(Edge(n0, n1))
df.add_edge(Edge(n1, n2))
@@ -107,22 +99,22 @@ def get_nearby_body_compiled_1(variable_map: dict[str, Any], key_stack: list[str
def test_nearby_hotels():
runtime = FlinkRuntime("test_nearby_hotels")
runtime.init()
- runtime.add_operator(FlinkOperator(hotel_op))
- runtime.add_stateless_operator(FlinkStatelessOperator(get_nearby_op))
+ runtime.add_operator(hotel_op)
+ runtime.add_stateless_operator(get_nearby_op)
# Create Hotels
hotels = []
- init_hotel = OpNode(hotel_op, InitClass())
+ init_hotel = OpNode(Hotel, InitClass(), read_key_from="name")
random.seed(42)
for i in range(20):
coord_x = random.randint(-10, 10)
coord_y = random.randint(-10, 10)
hotel = Hotel(f"h_{i}", Geo(coord_x, coord_y))
- event = Event(init_hotel, [hotel.name], {"name": hotel.name, "loc": hotel.loc}, None)
+ event = Event(init_hotel, {"name": hotel.name, "loc": hotel.loc}, None)
runtime.send(event)
hotels.append(hotel)
- collected_iterator: CloseableIterator = runtime.run(run_async=True, collect=True)
+ collected_iterator: CloseableIterator = runtime.run(run_async=True, output='collect')
records = []
def wait_for_event_id(id: int) -> EventResult:
for record in collected_iterator:
@@ -145,12 +137,11 @@ def wait_for_n_records(num: int) -> list[EventResult]:
print("creating hotels")
# Wait for hotels to be created
wait_for_n_records(20)
- time.sleep(3) # wait for all hotels to be registered
+ time.sleep(10) # wait for all hotels to be registered
dist = 5
loc = Geo(0, 0)
- # because of how the key stack works, we need to supply a key here
- event = Event(n0, ["workaround_key"], {"loc": loc, "dist": dist}, df)
+ event = Event(n0, {"loc": loc, "dist": dist}, df)
runtime.send(event, flush=True)
nearby = []
diff --git a/tests/integration/flink-runtime/test_two_entities.py b/tests/integration/flink-runtime/test_two_entities.py
index 9d2e0cf..3d89bd2 100644
--- a/tests/integration/flink-runtime/test_two_entities.py
+++ b/tests/integration/flink-runtime/test_two_entities.py
@@ -10,24 +10,24 @@
def test_two_entities():
runtime = FlinkRuntime("test_two_entities")
runtime.init()
- runtime.add_operator(FlinkOperator(item_op))
- runtime.add_operator(FlinkOperator(user_op))
+ runtime.add_operator(item_op)
+ runtime.add_operator(user_op)
# Create a User object
foo_user = User("foo", 100)
- init_user_node = OpNode(user_op, InitClass())
- event = Event(init_user_node, ["foo"], {"key": "foo", "balance": 100}, None)
+ init_user_node = OpNode(User, InitClass(), read_key_from="key")
+ event = Event(init_user_node, {"key": "foo", "balance": 100}, None)
runtime.send(event)
# Create an Item object
fork_item = Item("fork", 5)
- init_item_node = OpNode(item_op, InitClass())
- event = Event(init_item_node, ["fork"], {"key": "fork", "price": 5}, None)
+ init_item_node = OpNode(Item, InitClass(), read_key_from="key")
+ event = Event(init_item_node, {"key": "fork", "price": 5}, None)
runtime.send(event)
# Create an expensive Item
house_item = Item("house", 1000)
- event = Event(init_item_node, ["house"], {"key": "house", "price": 1000}, None)
+ event = Event(init_item_node, {"key": "house", "price": 1000}, None)
runtime.send(event)
# Have the User object buy the item
@@ -35,10 +35,10 @@ def test_two_entities():
df = user_op.dataflows["buy_item"]
# User with key "foo" buys item with key "fork"
- user_buys_fork = Event(df.entry, ["foo"], {"item_key": "fork"}, df)
+ user_buys_fork = Event(df.entry, {"user_key": "foo", "item_key": "fork"}, df)
runtime.send(user_buys_fork, flush=True)
- collected_iterator: CloseableIterator = runtime.run(run_async=True, collect=True)
+ collected_iterator: CloseableIterator = runtime.run(run_async=True, output="collect")
records = []
def wait_for_event_id(id: int) -> EventResult:
@@ -53,8 +53,8 @@ def wait_for_event_id(id: int) -> EventResult:
assert buy_fork_result.result == True
# Send an event to check if the balance was updated
- user_get_balance_node = OpNode(user_op, InvokeMethod("get_balance"))
- user_get_balance = Event(user_get_balance_node, ["foo"], {}, None)
+ user_get_balance_node = OpNode(User, InvokeMethod("get_balance"), read_key_from="key")
+ user_get_balance = Event(user_get_balance_node, {"key": "foo"}, None)
runtime.send(user_get_balance, flush=True)
# See that the user's balance has gone down
@@ -63,7 +63,7 @@ def wait_for_event_id(id: int) -> EventResult:
# User with key "foo" buys item with key "house"
foo_user.buy_item(house_item)
- user_buys_house = Event(df.entry, ["foo"], {"item_key": "house"}, df)
+ user_buys_house = Event(df.entry, {"user_key": "foo", "item_key": "house"}, df)
runtime.send(user_buys_house, flush=True)
# Balance becomes negative when house is bought