typesafe_carla
A statically typed CARLA client for Codon.
Programs written against it are type-checked by the Codon compiler before they
run, and they call LibCarla through a narrow C ABI. CPython and the carla
Python package are not involved.
import typesafe_carla as carla
client = carla.Client("localhost", 2000)
world = client.get_world()
vehicle = world.get_actors()[0].as_vehicle()
vehicle.apply_control(carla.VehicleControl(throttle=0.2, steer=0.0))
vehicle.apply_control(carla.Transform())
# error: 'Transform' does not match expected type 'VehicleControl' <- at compile time
See docs/design.md for the full design.
Status
Milestones (design section 43):
| Milestone | Status |
|---|---|
| 0: proof of concept | ✅ verified against a CARLA 0.10.0 server |
| 1: usable vehicle API | ✅ verified against a CARLA 0.10.0 server |
| 2: sensors | ✅ verified against a CARLA 0.10.0 server |
| 3: distribution | ✅ release pipeline verified end to end (manylinux wheels from CI, clean-container uv sync → build → ./main against a CARLA server); publishing to PyPI needs the one-time setup in docs/releasing.md |
| 4: broader compatibility | ✅ verified against a CARLA 0.10.0 server |
| 5: binding generation | ✅ 252 C ABI functions generated from bindings/*.yaml, spec validated against LibCarla 0.10.0 and ue5-dev with libclang, coverage report |
| Area | Implemented |
|---|---|
| Client | Client (host="127.0.0.1", port=2000, worker_threads=0), set_timeout, get_timeout, get_world, load_world (map_layers: MapLayer flags), reload_world, get_server_version, get_client_version, apply_batch, apply_batch_sync, get_trafficmanager, generate_opendrive_world (+ OpendriveGenerationParameters), get_available_maps, load_world_if_different (map_layers; → Optional[World]), file transfer: get_required_files, request_file, set_files_base_folder; recorder: start_recorder, stop_recorder, show_recorder_file_info, show_recorder_collisions, show_recorder_actors_blocked, replay_file, stop_replayer, set_replayer_time_factor, set_replayer_ignore_hero, set_replayer_ignore_spectator |
| World | id, get_actors (also get_actors(actor_ids: List[int]): unknown ids are left out), get_actor (→ Optional[Actor]), get_blueprint_library, spawn_actor, try_spawn_actor (→ Optional[Actor]; both with attach_to, attachment_type), tick, wait_for_tick, get_snapshot, get_map, get_settings, apply_settings, get_weather / set_weather / is_weather_enabled, get_random_location_from_navigation (→ Optional), debug (DebugHelper: draw_point, draw_line, draw_arrow, draw_box, draw_string, clear_debug_shape / clear_debug_string (LibCarla ue5-dev)), get_spectator, traffic lights and signs: get_traffic_light_from_opendrive_id (→ Optional[TrafficLight]), get_traffic_sign(landmark) (→ Optional[TrafficSign]), get_traffic_light(landmark) (→ Optional[TrafficLight]), get_traffic_lights_from_waypoint, get_traffic_lights_in_junction, freeze_all_traffic_lights, reset_all_traffic_lights; get_vehicles_light_states (→ Dict[int, int]); environment: get_environment_objects (EnvironmentObject), enable_environment_objects, get_level_bbs, get_names_of_all_objects (CityObjectLabel); queries: cast_ray, project_point / ground_projection (→ Optional[LabelledPoint]); load_map_layer / unload_map_layer (MapLayer); set_pedestrians_seed, set_pedestrians_cross_factor, get_imu_sensor_gravity / set_imu_sensor_gravity (LibCarla ue5-dev); on_tick / remove_on_tick (dispatched like sensor callbacks); textures: apply_color_texture_to_object(s), apply_float_color_texture_to_object(s), apply_textures_to_object(s) |
| Actor | id, type_id, is_alive, bounding_box, get/set_transform, get/set_location, get_velocity, set_target_velocity, get_acceleration, get_angular_velocity, destroy, set_target_angular_velocity, add_impulse, add_force, add_angular_impulse, add_torque, set_simulate_physics, set_enable_gravity, get_world (issue #33); issue #19: actor_state (ActorState), is_active, is_dormant, attributes (Dict[str, str]), parent (→ Optional[Actor]), semantic_tags (List[int] of CityObjectLabel), get_actor_name, get_actor_class_name, set_collisions, enable_constant_velocity / disable_constant_velocity, add_force_at_location, add_impulse_at_location, apply_texture (TextureColor / TextureFloatColor, MaterialParameter), skeleton queries get_bone_names, get_bone_world_transforms, get_bone_relative_transforms, get_component_names, get_component_world_transform, get_component_relative_transform, get_socket_names, get_socket_world_transforms, get_socket_relative_transforms (LibCarla newer than 0.10.0 only, see below); checked as_vehicle / as_sensor / as_walker / as_walker_ai_controller / as_traffic_light / as_traffic_sign; the methods of Vehicle, Walker, WalkerAIController, TrafficLight, TrafficSign (trigger_volume, for signs and lights) and Sensor, as in the Python API (on a plain Actor only; checked at run time, ActorTypeError on the wrong kind; compile errors with --strict) |
| Vehicle | apply_control, get_control, set_autopilot, get_physics_control / apply_physics_control (every LibCarla UE5 field), set_light_state / get_light_state (VehicleLightState), get_speed_limit, get_traffic_light_state, is_at_traffic_light, get_traffic_light (→ Optional); issue #20: apply_ackermann_control (VehicleAckermannControl), get_ackermann_controller_settings / apply_ackermann_controller_settings (AckermannControllerSettings), open_door / close_door (VehicleDoor), get_failure_state (VehicleFailureState), get_telemetry_data (VehicleTelemetryData, WheelTelemetryData; LibCarla ue5-dev only), show_debug_telemetry, get_wheel_steer_angle / set_wheel_steer_direction (VehicleWheelLocation), get_vehicle_bone_world_transforms (LibCarla ue5-dev only), enable_carsim, use_carsim_road, enable_chrono_physics (server plugins) |
| Walkers | Walker (apply_control(WalkerControl), get_control; issue #20: get_bones (→ WalkerBoneControlOut of bone_transform_out), set_bones (WalkerBoneControlIn of bone_transform or (name, Transform) pairs), blend_pose, show_pose, hide_pose, get_pose_from_animation), WalkerAIController (start, stop, go_to_location, set_max_speed) |
| Traffic signs | TrafficSign (trigger_volume), from as_traffic_sign() on a traffic sign or a traffic light (issue #19) |
| Traffic lights | TrafficLight (state / get_state / set_state (TrafficLightState), green/yellow/red times, get_elapsed_time, freeze, is_frozen, get_pole_index, reset_group, get_opendrive_id, trigger_volume, get_affected_lane_waypoints, get_stop_waypoints, get_group_traffic_lights, get_light_boxes) |
| Lights | World.get_lightmanager(), LightManager (get_all_lights, get_turned_on_lights / get_turned_off_lights, turn_on / turn_off, set_active / is_active, set_color(s) / get_color, set_intensity / set_intensities / get_intensity, set_light_group(s) / get_light_group, set_light_state(s) / get_light_state, set_day_night_cycle), Light (id, location, color, intensity, light_group, light_state, is_on, turn_on / turn_off, set_color, set_intensity, set_light_group, set_light_state), LightGroup, LightState |
| Traffic Manager | TrafficManager (set_synchronous_mode, set_random_device_seed, set_hybrid_physics_mode, global_percentage_speed_difference, set_global_distance_to_leading_vehicle, per-vehicle vehicle_percentage_speed_difference, distance_to_leading_vehicle, random_left/right_lanechange_percentage, ignore_lights/signs/vehicles/walkers_percentage, keep_right_rule_percentage / keep_slow_lane_rule_percentage, set_desired_speed, vehicle_lane_offset, auto_lane_change, force_lane_change, update_vehicle_lights, get_port, global_lane_offset, collision_detection (any actor as the other one), set_osm_mode, set_respawn_dormant_vehicles, set_boundaries_respawn_dormant_vehicles, set_hybrid_physics_radius, set_path (List[Location]), set_route (road option names), get_next_action (→ Tuple[str, Waypoint]), get_all_actions (→ List[Tuple[str, Waypoint]]), shut_down; global_large_vehicle_wide_turn / vehicle_large_vehicle_wide_turn (LibCarla ue5-dev)) |
| Weather | WeatherParameters (all 14 fields; LibCarla's 23 named presets as class attributes, WeatherParameters.ClearNoon & co., shared objects as in the Python API, and as copies through WeatherParameters.preset("ClearNoon")) |
| Map | carla.Map(name, xodr_content) (client-side, from an OpenDRIVE string), name, get_spawn_points, get_waypoint (→ Optional[Waypoint]), get_waypoint_xodr (→ Optional[Waypoint]), generate_waypoints, to_opendrive, save_to_disk, cook_in_memory_map, get_topology, get_crosswalks, get_all_landmarks, get_all_landmarks_of_type, get_all_landmarks_from_id, get_landmark_group; geo-referencing: get_georeference, transform_to_geolocation, and with CARLA ue5-dev get_geoprojection, geolocation_to_transform and explicit projections (GeoLocation, GeoEllipsoid, GeoOffsetTransform, GeoProjectionTM / UTM / WebMerc / LCC2SP, GeoProjection); LaneType, Junction (id, bounding_box, get_waypoints) |
| Waypoint | id, transform, road_id, section_id, lane_id, s, is_junction, is_intersection, junction_id, lane_width, lane_type, lane_change (LaneChange), left_lane_marking / right_lane_marking (→ Optional[LaneMarking]: LaneMarkingType, LaneMarkingColor), is_rht, next, previous, next_until_lane_end, previous_until_lane_start, get_left_lane / get_right_lane / get_junction (→ Optional), get_landmarks, get_landmarks_of_type |
| Landmark | every field of the Python API (id, name, type, road_id, s, t, distance, orientation (LandmarkOrientation), h_offset, pitch, roll, is_dynamic, ...), waypoint (→ Optional[Waypoint]), get_lane_validities; LandmarkType |
| Snapshots | WorldSnapshot (id, frame / frame_count, timestamp, elapsed_seconds, delta_seconds, platform_timestamp, find → Optional, has_actor, indexing, iteration), ActorSnapshot, Timestamp (frame / frame_count) |
| Sensors | Actor.as_sensor() (checked), Sensor.listen(callback) (dispatched on the program's thread at tick / wait_for_tick / carla.dispatch_sensor_callbacks(), or on the callback thread: carla.start_callback_thread() / stop_callback_thread() / callback_thread_running(), issue #86), Sensor.listen(queue_size) / stop / destroy / poll (→ Optional[SensorData]) / wait_for_data / has_callback / pending_count / dropped_count, enable_for_ros / disable_for_ros / is_enabled_for_ros, listen_to_gbuffer / is_listening_gbuffer / stop_gbuffer (GBufferTextureID; issue #33); SensorData (frame, frame_number, timestamp, transform) with checked as_image() / as_lidar() / as_semantic_lidar() / as_radar() / as_gnss() / as_imu() / as_collision() / as_obstacle() / as_lane_invasion() / as_dvs() / as_optical_flow(); Image (raw_data → read-only RawData view, zero-copy raw_data_ptr(), indexing and iteration → Color, pixel, convert(ColorConverter), save_to_disk(path, ColorConverter) as PNG), ColorConverter (Raw, Depth, LogarithmicDepth, CityScapesPalette), LidarMeasurement (raw_data, zero-copy raw_data_ptr() / raw_points(), iteration, get_point_count, save_to_disk as PLY), SemanticLidarMeasurement / SemanticLidarDetection, RadarMeasurement / RadarDetection, DVSEventArray / DVSEvent (to_image, to_array, to_array_x/y/t/pol), OpticalFlowImage / OpticalFlowPixel (get_color_coded_flow → FakeImage) — all zero-copy views of LibCarla's buffers; GnssMeasurement, IMUMeasurement, CollisionEvent / ObstacleDetectionEvent (actor, other_actor → Optional[Actor], distance), LaneInvasionEvent (crossed_lane_markings → List[LaneMarking], actor), LaneInvasionSensor, ServerSideSensor, ClientSideSensor, LaneMarking / LaneMarkingType / LaneMarkingColor / LaneChange; V2X (issue #42, LibCarla ue5-dev only): Sensor.send(CustomV2XBytes) / send(str), as_cam_event() → CAMEvent (get_message_count, indexing, iteration → CAMMessage: power, get() → typed CAM), as_custom_v2x_event() → CustomV2XEvent (→ CustomV2XData: power, get() → CustomV2XMessage), CustomV2XBytes (get_string / set_string / get_bytes / set_bytes / data_size / max_data_size / get); the Python API's dict keys on every get() result, resolved at compile time (get()["Message"]["Message"]["DataSize"], issue #85) |
| Batch commands | carla.command.SpawnActor(...).then(...), FutureActor, DestroyActor, ApplyVehicleControl, ApplyWalkerControl, ApplyTransform, ApplyLocation, ApplyTargetVelocity, ApplyTargetAngularVelocity, ApplyImpulse, ApplyForce, ApplyAngularImpulse, ApplyTorque, SetAutopilot, SetSimulatePhysics, SetEnableGravity, SetVehicleLightState, SetTrafficLightState, ApplyVehicleAckermannControl, ShowDebugTelemetry, ApplyVehiclePhysicsControl, ApplyWalkerState; CommandResponse |
| Blueprints | BlueprintLibrary (find, filter, filter_by_attribute, indexing, iteration), ActorBlueprint (id, tags, has_tag, match_tags, has_attribute, get_attribute, set_attribute, len() and iteration over its attributes), ActorAttribute (typed as_bool/as_int/as_float/as_str/as_color, recommended_values) |
| Values | TextureColor, TextureFloatColor, FloatColor, MaterialParameter, CityObjectLabel, ActorState (issue #19), MapLayer, EnvironmentObject, LabelledPoint (issue #21), Location, Rotation (get_forward_vector / get_right_vector / get_up_vector, get_normalized), Transform (get_forward_vector / get_right_vector / get_up_vector, get_matrix, get_inverse_matrix, transform (a point or a list of points, in place), transform_vector), Vector2D (length, squared_length, make_unit_vector), Vector3D, Velocity, AngularVelocity, Acceleration, Quaternion, BoundingBox (contains, get_local_vertices, get_world_vertices), VehicleControl, VehiclePhysicsControl and WheelPhysicsControl (all fields: curves, gear ratios, wheels), WorldSettings (every LibCarla field, including max_culling_distance, deterministic_ragdolls, tile_stream_distance, actor_active_distance, spectator_as_ego), Color, VehicleAckermannControl, AckermannControllerSettings, VehicleTelemetryData, WheelTelemetryData, WalkerBoneControlIn / WalkerBoneControlOut, bone_transform / bone_transform_out; enumerations VehicleDoor, VehicleWheelLocation, VehicleFailureState, AttachmentType; constants GBufferTextureID |
| Errors | CarlaError, TimeoutError, ActorTypeError, VersionError (plus IndexError for lookups by key or index) |
| Tooling | typesafe-codon launcher, typesafe-carla-toolchain (bundled Codon), uv workspace + uv.lock, CI, PyPI release workflow, binding generator (docs/bindgen.md) |
Notes on issue #22 (map, waypoint, landmark and traffic light gaps):
- Geo projections need CARLA ue5-dev.
get_georeferenceandtransform_to_geolocation(location)work with every LibCarla. CARLA 0.10.0's LibCarla has nogeom::GeoProjection: there,transform_to_geolocationuses the geo-reference's Mercator approximation (GeoLocation::Transform), andget_geoprojection,geolocation_to_transformand an explicit projection argument raiseCarlaError. Newer LibCarla (ue5-dev) projects with the map'sGeoProjection, which treats y differently: away from the origin the two give different latitudes. The officialcarla0.10.0 wheel on PyPI already has the newer code, so against a 0.10.0 server it can disagree with typesafe_carla built from the 0.10.0 tag (the compatibility test compares only the origin). get_geoprojection()returns aGeoProjection, a Union of the four projection classes; tell them apart withisinstance. Projection arguments are checked at compile time (a Union parameter would accept anything).Waypoint.is_rhtis alwaysTruewith CARLA 0.10.0, which has no left-hand traffic (its lane markings use the right-hand layout).Landmark.waypointisNonefor landmarks fromMap.get_all_landmarks*, as in LibCarla; landmarks fromWaypoint.get_landmarks*have one.TrafficLight.get_affected_lane_waypoints()/get_stop_waypoints()/get_group_traffic_lights()skip the null entries LibCarla can return (the Python API would putNonein the list).Waypoint.get_landmarks*with a negative or non-finite distance raisesCarlaError.Map.get_waypoint_xodrwith a road id outside uint32 or a lane id outside int32 returnsNone(the Python API raisesOverflowError).Map.save_to_diskraisesCarlaErrorwhen the file cannot be written (the Python API ignores it);cook_in_memory_map, like LibCarla, only logs it.carla.Map(name, xodr_content)(issue #39) parses the OpenDRIVE text in this process, with no server. Such a map has no recommended spawn points, as in LibCarla. Only text that does not parse as XML reliably raises:CarlaError"the OpenDRIVE document does not parse" (the Python API:RuntimeError: std::exception). LibCarla accepts any well-formed XML (<foo/>or<OpenDRIVE/>give an empty map); a well-formed but inconsistent OpenDRIVE document can raise another error (e.g.IndexErrorfrom anout_of_range) or crash inside LibCarla (e.g. a<road>without<planView>), as it does with the Python API. The mock backend has no OpenDRIVE parser: it accepts only a document with a closed<OpenDRIVE>element (<foo/>raises there) and models any such document, even<OpenDRIVE/>, as its own two-lane road.
Notes on issue #23 (Client, Traffic Manager, blueprint, debug and value-type gaps):
- Rotation convention follows LibCarla. CARLA ue5-dev flipped the sign of the pitch and roll terms of
Rotation's basis vectors and ofTransform's matrices relative to 0.10.0 (yaw-only rotations are the same).get_forward_vector,get_right_vector,get_up_vector,get_matrix,get_inverse_matrix,transform,transform_vectorandBoundingBox's methods take the rotation matrix from the LibCarla this library is built from, as the official Python API built from the same sources does; the rest of the math is Codon.Quaternion(only in ue5-dev's LibCarla and Python API) is pure Codon with ue5-dev's math, so with a LibCarla 0.10.0 build its basis vectors can differ fromRotation's for a non-zero pitch or roll. - ue5-dev only.
TrafficManager.global_large_vehicle_wide_turn/vehicle_large_vehicle_wide_turnandDebugHelper.clear_debug_shape/clear_debug_stringraiseCarlaErrorwith a LibCarla 0.10.0 build, which has no such functions. Clearing also needs a server with those RPCs (newer than 0.10.0). - Typed results.
Client.load_world_if_differentreturns the newWorld, orNonewhen the map is already loaded (the Python API always returnsNone).TrafficManager.get_next_actionreturns a(road option, Waypoint)tuple andget_all_actionsa list of them (Python: two-element lists).set_routeraisesCarlaErrorfor an unknown road option name (Python sends an invalid code).get_next_action/get_all_actionsraiseIndexErrorwhen there is no plan (a vehicle this Traffic Manager does not drive yet, or a shut-down Traffic Manager), with every LibCarla: ue5-dev returns an empty plan there and 0.10.0 throws. They need a Traffic Manager in this process: LibCarla'sTrafficManagerServerbinds these RPCs without returning the result (an upstream bug), so a remote one cannot answer.Transform.transform(point)returns the same type it is given (Python: aVector3D); like Python it also transforms the point in place. - Files.
get_required_files(download=True)andrequest_filewrite into the files base folder on this machine (~/carlaCache/by default,<folder>/<LibCarla version>/<file>), like the Python API. WorldSettings()has the Python API's defaults (deterministic_ragdolls=False,tile_stream_distance=3000,actor_active_distance=2000,spectator_as_ego=True);apply_settingssends every field, soworld.apply_settings(carla.WorldSettings(synchronous_mode=True))also resets the server's culling and streaming fields to these defaults, as in the Python API; modify the result ofget_settings()to keep them.- Constants only.
GBufferTextureIDis defined;Sensor.listen_to_gbufferand its companions take it (issue #33). AttachmentTypeis a distinct type likeVehicleDoor(issue #34): an int is a compile error and a value outside the enumeration raisesCarlaError, fromtry_spawn_actortoo. The batchcommand.SpawnActorhas no attachment type, as in the Python API.
Notes on issue #21 (World API gaps):
on_tickcallbacks run on the program's thread, like sensor callbacks: LibCarla's thread only queues each tick'sWorldSnapshot, and the callback runs atWorld.tick(),World.wait_for_tick(),Client.apply_batch(_sync)(do_tick=True)andcarla.dispatch_callbacks()(dispatch_sensor_callbacks(), the earlier name, does the same). LibCarla publishes a tick before it runs its OnTick callbacks, sotick()andwait_for_tick()first wait (within their timeout, usually microseconds) until the client's on_tick callbacks have received the returned frame; then that frame's snapshot is delivered by the same call (unless the wait times out).apply_batch(do_tick=True)does not wait. The queue is unbounded by default (on_tick(cb, queue_size=n)keeps the newest n). As in LibCarla, callbacks belong to the client: they keep running afterload_world, andremove_on_tick(id)works through any World of the same Client. It drops the snapshots not yet delivered; an unknown id is ignored.- Light manager. LibCarla (0.10.0 and ue5-dev) has
World::GetLightManager, but the official CARLA 0.10.0 Python API does not exposeWorld.get_lightmanager(0.10.0 has no night mode); here it is available. Lights cross the C ABI as ids; changes reach the server at the next tick. Bulk setters take one value per light (a length mismatch raisesCarlaError; LibCarla silently uses the shorter length). Light ids are the server's; a light id the manager does not know raisesIndexErrorand changes nothing (LibCarla would modify a placeholder state). get_imu_sensor_gravity/set_imu_sensor_gravityexist in LibCarla ue5-dev only (not in LibCarla 0.10.0's source, although the 0.10.0 Python wheel has them). Built against CARLA 0.10.0 they raiseCarlaError.get_traffic_sign(landmark)/get_traffic_light(landmark)returnNonewhen no actor matches the landmark's id (TrafficSign/TrafficLightotherwise).get_traffic_lights_in_junction(id)returns an empty list for an id that names no junction (LibCarla dereferences a null junction there).EnvironmentObject.idis LibCarla'suint64bit for bit, so ids at or above 2^63 read as negativeints; pass them back unchanged.- Textures:
get/setoutside the texture raiseIndexError(LibCarla does not check).FloatColorchannels are stored as float32.
Notes on Milestone 4:
- C ABI 2.0.
tsc_command_tgained a field (walker speed), an incompatible change made before the first release. The Codon module checks the major version on import. - Weather depends on the server. It can be disabled there: CARLA 0.10.0 (the Docker image used for testing) reports
is_weather_enabled() == False, andset_weatherhas no effect, exactly as with the official Python API. - Traffic Manager. It runs inside LibCarla in the client process. In synchronous mode, call
tm.set_synchronous_mode(True)as well. - Enumerations.
TrafficLightState,VehicleLightStateandLaneTypeare integer constants, as in the Python API (VehicleLightStatevalues combine with|). names/values(issue #33). Every enumeration has the Python API'snames(member name → member) andvalues(value → member; where members share a value, the last one declared, e.g.LaneMarkingColor.values[0]isWhite). The integer enumerations (TrafficLightState,LaneType,CityObjectLabel, ...) are each the one instance of a class whose fields are the members, sonames/valuescome from one shared implementation (_enum.codon); a misspelt member is a compile error naming that class ('_TrafficLightState' object has no attribute 'Purple'). Theirnamesandvaluesbuild a new dict on each access; in the Python API they are shared class attributes. The members are the same as the official CARLA 0.10.0 module's (checked bytests/test_runtime.py).
Notes on issue #20 (Vehicle and Walker API gaps):
- Typed enumerations.
VehicleDoor,VehicleWheelLocationandVehicleFailureStateare distinct types, notintconstants (unlikeTrafficLightStateand the bit-flag enumerations):vehicle.open_door(carla.VehicleWheelLocation.FL_Wheel)andvehicle.open_door(0)do not compile. The official API rejects anintthere too.int(carla.VehicleDoor.All)gives the value.VehicleDoorhas the Python API's members (FL,FR,RL,RR,All); LibCarla'sHoodandTrunkhave no named members, as in the Python API; their raw values 4 and 5 (carla.VehicleDoor(4)) pass through. - Depends on the LibCarla version.
Vehicle.get_telemetry_data()andVehicle.get_vehicle_bone_world_transforms()call LibCarla methods that ue5-dev has and CARLA 0.10.0 does not. Built against LibCarla 0.10.0, they raiseCarlaError("... is not available in LibCarla 0.10.0"); the rest of the library is unaffected. - Recorder and replayer arguments (issue #36).
Client.replay_file(name, time_start, duration, follow_id, replay_sensors, replay_weather, offset, map_override)andClient.start_recorder(name, additional_data, stop_replayer)have the official arguments and keywords (show_recorder_*takename, andshow_recorder_collisionstype1/type2).replay_weather,offsetandmap_override, andstop_replayer, exist only in LibCarla ue5-dev: while they keep their defaults (False,Transform(),"";True) the call is the one LibCarla 0.10.0 also has, so they work everywhere; another value needs ue5-dev, and built against 0.10.0 raisesCarlaError("... is not available in LibCarla 0.10.0").offsetmay also beNone(the default). - Server plugins.
enable_carsim,use_carsim_roadandenable_chrono_physicsare bound (one LibCarla call each), but they only do something on a server built with the CarSim or Chrono plugin, which CARLA UE5 does not ship. LibCarla sends them asynchronously, so an unsupported server ignores them without an error.enable_chrono_physics's fourth keyword ispowetrain_json, spelled as in the official Python API.restore_physx_physicsis not bound: neither LibCarla 0.10.0 nor ue5-dev has a counterpart (the official module lists it, but there is no LibCarla method to call). - Fire and forget.
open_door,close_door,set_wheel_steer_direction,show_debug_telemetry,apply_ackermann_controller_settings,set_bones,blend_pose/show_pose/hide_poseandget_pose_from_animationare asynchronous RPCs in LibCarla: the server's errors (an unknown bone name, a door a vehicle does not have) are not reported.set_wheel_steer_directionturns the wheel's bone only;get_wheel_steer_anglereports the physics angle.
Notes on Milestone 5:
- Generated plumbing, hand-written API. 252 C ABI functions are generated from
bindings/*.yaml: the C declarations, the C++ shim and the Codon FFI. Each one is a handle check, argument conversions and a single LibCarla call. The ABI is unchanged; libclang compared every prototype and struct size before and after the migration. See docs/bindgen.md. - Validated against LibCarla.
tools.bindgen validateparses the shim with libclang and checks each spec'd method's existence, arity and types. It runs against the mock headers and against LibCarla ue5-dev and 0.10.0 in CI. - Coverage. docs/coverage.md lists the public methods of the main LibCarla client classes and whether the shim calls them (generated, hand-written or not yet).
Notes on Milestone 2:
- No callbacks on LibCarla threads (design §15). LibCarla's threads only fill a per-sensor queue.
listen(callback)runs the callback on the program's own thread, atWorld.tick(),World.wait_for_tick(),Client.apply_batch(_sync)(do_tick=True)andcarla.dispatch_sensor_callbacks(); or, aftercarla.start_callback_thread()(issue #86), on one background thread as soon as data arrives, as the Python API does. Its queue is unbounded by default.stop(),destroy()(through any handle) and batchDestroyActorunregister it.listen()/listen(queue_size)is polling mode: the program reads a bounded queue (64 by default) withpoll()/wait_for_data(), and the oldest measurement is dropped when it is full (dropped_count).queue_size=0means unbounded in both modes. Callstop()(or destroy the sensor) when done. - Zero copy (design §16).
Image.raw_data_ptr()(BGRA) andLidarMeasurement.raw_points()point into LibCarla's buffer and stay valid while the measurement object is alive. Theraw_dataproperty (issue #78) is a read-only view of the same bytes that keeps the measurement alive itself. World.spawn_actor(..., attach_to=...)accepts any actor subclass (e.g. aVehicle) or anOptionalof one, and rejects non-actors at compile time.
Notes on Milestone 1:
- Physics control.
VehiclePhysicsControlandWheelPhysicsControlhave every field of LibCarla UE5'srpc::VehiclePhysicsControl/rpc::WheelPhysicsControl(identical in 0.10.0 and ue5-dev), with the Python API's names and LibCarla's defaults: the torque and steering curves and each wheel'slateral_slip_graph(List[Vector2D]),forward_gear_ratios/reverse_gear_ratios(List[float]; the official 0.10.0 Python API cannot read or set these), the engine, transmission, chassis and suspension scalars, and theuint8_tcodes (differential_type,axle_type, ...) asintin [0, 255]. The lists are plain Codon lists, sopc.wheels[0].wheel_radius = 40.0changespcin place.apply_physics_controlreads the vehicle's current control and overwrites every field, so fields a newer LibCarla adds keep the server's values; the wheel count must match the vehicle's. CARLA 0.10.0 applies changes a few frames later, ignores per-wheel fields such asmax_brake_torque,max_steer_angle,wheel_radiusandcornering_stiffness, and puts two default keys in front of every curve it is given (so each apply growstorque_curveandsteering_curveby two points), all exactly as the official Python API reads back. - Batch commands. As in the Python API, a command's actor (and
SpawnActor's parent) is anActorof any kind or its id, given positionally or by the official keywordsactor_id=/actor=(parent_id=/parent=); passing both, or neither, is a compile error. AnOptionalactor such asworld.get_actor(id)is accepted too and raisesCarlaErrorwhen it isNoneat run time; a literalNone(on which the official 0.10.0 module crashes), anOptional[int]or any other type is a compile error. Unlike the official overloads, either keyword takes either form (actor_id=vehicleworks). Every constructor returns oneCommandtype, so one list can mix command kinds.SetAutopilotinapply_batch_syncalso registers the vehicle with the Traffic Manager, like the Python API.ApplyVehiclePhysicsControlcopies the control when the command is made and sends all of it; unlikeVehicle.apply_physics_control(), it cannot check the wheel count against the vehicle's.
Supported CARLA versions
CARLA UE5 only (the CMake-based ue5-dev branch, 0.10.x and later).
UE4 (0.9.x, ue4-dev) is not supported.
LibCarla is built from CARLA sources as part of the build. The default is the
latest ue5-dev; any branch, tag or commit SHA can be selected:
| How | Example |
|---|---|
| environment variable | CARLA_GIT_REF=0.10.0 |
| CMake | -DTSC_CARLA_GIT_REF=<branch|tag|sha> |
| pip / uv build setting | -C cmake.define.TSC_CARLA_GIT_REF=<ref> |
| local checkout | CARLA_SOURCE_DIR=~/carla or -DTSC_CARLA_SOURCE_DIR=... |
| other repository (fork) | -DTSC_CARLA_GIT_REPOSITORY=https://github.com/<you>/carla.git |
| ref name to record for a SHA | -DTSC_CARLA_REF_NAME=ue5-dev (or CARLA_REF_NAME) |
Released wheels name their CARLA ref in the release tag,
<version>-<CARLA ref>-<YYYYMMDD> (e.g. 0.1.0-ue5-dev-20260915: ue5-dev
as of its last commit on that day); see docs/releasing.md.
Only CMakeLists.txt, CMake/ and LibCarla/ are fetched, as a shallow,
blob-filtered, sparse checkout (a few MB, not the multi-GB repository). A
moving branch is re-fetched only on -DTSC_CARLA_REFRESH=ON. The ref and the
resolved commit are compiled in: typesafe-codon info,
carla.libcarla_git_ref() / carla.libcarla_git_commit() in Codon, and
_native/BUILD_INFO.json in the wheel. When the ref is a commit SHA resolved
from a branch or tag (CI and releases build that way), TSC_CARLA_REF_NAME
names that branch or tag, and it is what is recorded as the ref: e.g.
ue5-dev with commit 1360bb9….
| typesafe_carla | ABI | Codon | Python | CARLA | Platform | Tested |
|---|---|---|---|---|---|---|
| 0.1.0 | 4.5 | 0.19.x | ≥ 3.10 (launcher only) | UE5: ue5-dev (default), 0.10.0 |
Linux x86_64; Ubuntu 22.04 (GCC 11), 24.04 (GCC 13), 26.04 (GCC 15) | 0.10.0: integration and compatibility tests pass against a CARLA 0.10.0 server. ue5-dev and 0.10.0: build, link and pass the C ABI tests in CI on each of those Ubuntu LTS releases with its default GCC |
Backends
TSC_BACKEND selects the implementation behind the C ABI. The shim in
native/src is shared by both:
libcarla(default): LibCarla built from CARLA UE5 sources as above, linked statically intolibtypesafe_carla_ffi.so. The resulting library depends only on libstdc++/libc and exports only thetsc_*functions.mock: an in-memory stand-in for LibCarla (native/mock) with the same class and method signatures, a fake "server" perhost:port, and a toy vehicle model. It is used for the compile and runtime test suites and for development without CARLA. It is not a simulator.
typesafe-codon info and typesafe_carla.backend() report which one you
have.
Installation
See docs/usage.md for using typesafe_carla from your own project. Once it is published:
uv add typesafe-carla # or: pip install typesafe-carla
uv run typesafe-codon run main.py
Python 3.10 or newer (CI tests 3.10 and 3.14). Python only runs the
typesafe-codon launcher; your programs are compiled by Codon.
typesafe-carla depends on typesafe-carla-toolchain, which bundles a
pinned Codon. Requirements and building for another CARLA ref are in
docs/usage.md; the release process is in
docs/releasing.md.
From CPython: import typesafe_carla.carla as carla
CPython programs (a scenario framework, a test harness) use the same library
with the CARLA Python API's names, in place of the carla package:
import typesafe_carla.carla as carla
client = carla.Client("localhost", 2000)
world = client.get_world()
typesafe_carla.carla is the library compiled with codon build --pyext
(typesafe_carla.pycarla; CARLA's own PythonAPI tests run unmodified on it,
see below). The released wheel carries it prebuilt
(typesafe_carla/carla/_prebuilt, added by the release workflow): pip install typesafe-carla imports it with nothing to compile. One build serves
every Python 3.10+: Codon's output does not depend on the Python version, and
the package finds the native library and the Codon runtime at run time.
Where no prebuilt package matches the installation (a source checkout, an
sdist install, edited Codon sources, another typesafe-carla-toolchain
release), the first import builds it instead, into
~/.cache/typesafe_carla/pycarla/<key>; that takes 15 to 50 minutes and ~14 GB
of memory and needs cc. typesafe-codon pycarla does the same ahead of time
(and says so when the wheel's build already applies).
TYPESAFE_CARLA_PYCARLA_DIR chooses the build directory (and wins over the
prebuilt package), and TYPESAFE_CARLA_PYCARLA_BUILD=0 makes a missing build
an ImportError instead of a build (python/typesafe_carla/carla_build.py).
It does not take the carla import name, so it can sit next to the official
package.
The same import compiles with Codon (codon/typesafe_carla/carla.codon is the
library under that name), so one source both runs on CPython and is checked by
the Codon compiler. Codon 0.19 resolves the module only in this form, not as
from typesafe_carla import carla.
Testing against a real CARLA server
tests/integration/*.codon and tests/compatibility/ need a running CARLA
UE5 server and the libcarla backend built from the matching ref (a
0.10.0 server needs -DTSC_CARLA_GIT_REF=0.10.0):
cmake -S . -B build-carla -DTSC_CARLA_GIT_REF=0.10.0 && cmake --build build-carla -j
export TSC_CARLA_HOST=localhost TSC_CARLA_PORT=2000 TYPESAFE_CARLA_BUILD_DIR=build-carla
uv run pytest tests/test_integration.py -s
# Same scenario through the official Python API and typesafe_carla, compared:
CARLA_PYTHON=/path/to/venv-with-carla/bin/python uv run python tests/compatibility/compare.py
CARLA's own PythonAPI tests
CARLA's test suite (PythonAPI/test: unit, smoke, API and the
top-level files; the few that need a map no ue5-dev package ships run as
ports under tests/upstream/ported, and interactive scripts are skipped)
runs against typesafe_carla, from the CARLA commit
the native library was built from, in two modes:
- cpython: the tests run unmodified under CPython, with
import carlabeing typesafe_carla built as a CPython extension (tools/pycarla.py,codon build --pyext); - codon: the tests are converted mechanically and compiled with
typesafe-codon.
tests/upstream/expectations.yaml
records the expected result per mode, CARLA ref and test. pytest and CI fail
on an unexpected failure and on an unexpected pass, so that list only shrinks.
tests/upstream/GAPS.md groups the failures by root
cause, separating missing typesafe_carla features from harness and Codon
limits. unit needs no server and runs in pytest and in CI: codon mode on
every libcarla leg, cpython mode on one leg per CARLA ref. The others run
only with TSC_CARLA_PORT set. See
tests/upstream/README.md.
uv run python -m tools.upstream_tests --mode all --suite unit -v
TSC_CARLA_HOST=127.0.0.1 TSC_CARLA_PORT=2000 TYPESAFE_CARLA_BUILD_DIR=build-carla \
uv run python -m tools.upstream_tests --mode all --suite smoke --suite API --suite top -j1 -v
Quick start (development)
Requirements: Linux x86_64, a C++20 compiler (GCC ≥ 11; CI builds with the default GCC of
Ubuntu 22.04, 24.04 and 26.04), git, CMake ≥ 3.27.2, and uv.
Codon is installed by uv sync from the toolchain/ workspace member.
TSC_BACKEND=mock uv sync # fast: editable install with the mock backend
uv run typesafe-codon info
# Mock backend: everything the test suites need.
cmake -S . -B build -DTSC_BACKEND=mock && cmake --build build -j
ctest --test-dir build # C ABI tests
uv run pytest # compile-pass/fail, runtime and launcher tests
uv run typesafe-codon run examples/connect.py
# Real backend: LibCarla from CARLA ue5-dev (first build fetches and compiles
# LibCarla and its dependencies; takes a while).
cmake -S . -B build-carla -DTSC_CARLA_GIT_REF=ue5-dev && cmake --build build-carla -j
TYPESAFE_CARLA_BUILD_DIR=build-carla uv run typesafe-codon run examples/connect.py
If GitHub archive downloads are blocked by your network but git works, add
-DPREFER_CLONE=ON; CARLA then clones its dependencies instead.
To skip compiling LibCarla, add -DTSC_CARLA_PREBUILT=auto: CMake then
downloads the LibCarla that CI built for the same CARLA commit and the same
compiler (Ubuntu 22.04/GCC 11, 24.04/GCC 13 or 26.04/GCC 15, from a run on
main), and only compiles the shim. This needs an authenticated
gh (gh auth login), because GitHub serves
workflow artifacts only with a token. If there is no matching prebuilt, CMake
builds LibCarla from source as usual. -DTSC_CARLA_PREBUILT_DIR=<prefix> uses
a prefix you already have, e.g. from tools/fetch_libcarla_prebuilt.py <ref>
or cmake --build <build dir> --target libcarla_prebuilt. Downloads are
cached (about 155 MB each) in ~/.cache/typesafe-carla/libcarla-prebuilt
(or under $TYPESAFE_CARLA_CACHE_DIR) and removed after 30 days unused;
tools/fetch_libcarla_prebuilt.py --prune empties it. See
docs/releasing.md.
typesafe-codon passes its arguments to codon after setting CODON_PATH
(the Codon sources), TYPESAFE_CARLA_LIB (the native library) and
LD_LIBRARY_PATH. For build, it also gives the executable an RPATH to the
native library, so the result runs without the launcher.
Continuing a cloud session locally
Claude Code sessions on the web can be pulled into a local terminal on any machine. Run this from a checkout of this repository, logged in to the same claude.ai account:
claude --teleport <session-id> # or plain `claude --teleport` for a picker
Teleport fetches and checks out the session's branch (it must be pushed) and
restores the conversation. The working tree must be clean; you are prompted
to stash otherwise. It requires claude.ai login (not an API key). Project
context for new sessions is in CLAUDE.md.
Repository layout
cmake/FetchCarla.cmake fetches CARLA UE5 sources at a ref (default ue5-dev)
codon/typesafe_carla/ Codon API (what users import)
_ffi.codon raw C declarations, POD mirrors, handle ownership
native/include/.../ffi.h the C ABI
native/src/ C ABI implementation over LibCarla
native/mock/ in-memory LibCarla stand-in (mock backend)
python/typesafe_carla/ typesafe-codon launcher, path and toolchain discovery
toolchain/ typesafe-carla-toolchain: pinned Codon as a wheel
tools/check_wheel.py release checks on a built wheel
tools/release_tag.py release tags <version>-<CARLA ref>-<YYYYMMDD>
tools/bump_version.py bumps the package version (Python and Codon)
tools/bindgen/ binding generator, libclang spec validation, coverage
bindings/ binding spec (YAML) for the generated C ABI functions
native/src/generated/ generated C++ shim (do not edit)
.github/workflows/ CI (mock + LibCarla builds) and PyPI release
tests/native/ C ABI tests (ctest)
tests/compile/pass|fail/ programs that must / must not compile
tests/compile/strict_fail/ programs that compile only outside strict mode
tests/unit/ Codon runtime tests against the mock backend
tests/upstream/ CARLA's own PythonAPI tests: expectations, unittest shim
tools/upstream_tests.py runs those tests (cpython and codon modes), writes GAPS.md
tools/pycarla.py the checkout's `carla` build for them (typesafe_carla.pycarla, --package carla)
python/typesafe_carla/pycarla/ typesafe_carla as a CPython package (codon --pyext): generator, runtime, pruned-overload cache
python/typesafe_carla/carla/ `import typesafe_carla.carla as carla` (built on first use, carla_build.py)
examples/ example programs
Writing new scenarios: use the statically checked style
typesafe_carla has two ways to write the same thing. The compatibility style exists so that code written for the CARLA Python API ports with few changes. The statically checked style is what the type checker can verify at compile time. For new scenarios, write the statically checked style, and enforce it with strict mode.
| Compatibility style (ported code) | Statically checked style (recommended) | |
|---|---|---|
Vehicle methods on an actor from get_actor |
world.get_actor(i).apply_control(c): the actor kind is checked at run time (ActorTypeError) |
world.get_actor(i).as_vehicle().apply_control(c), or keep the Vehicle from spawn_actor(...).as_vehicle() |
| A position where a vector is expected | actor.set_target_velocity(target_location): compiles, and a position silently becomes a velocity |
actor.set_target_velocity(target_location.as_vector()), or compute a real Vector3D |
| A vector where a position is expected | carla.Transform(loc + offset, rot) |
carla.Transform(carla.Location(loc + offset), rot) |
Use the statically checked style even where the compatibility style happens to work. The compatibility style moves mistakes that would be compile errors (the wrong actor kind, a position passed as a velocity) to run time, or makes them silent.
Warnings. Every compatibility path a program uses is reported twice. At
compile time, typesafe-codon lists every one the program contains before it
runs:
typesafe-codon: compile-time warning: Actor.apply_control(VehicleControl) without as_vehicle(): Python-API compatibility path, not statically checked (use --strict to make this an error)
At run time, each one is reported once, when it is first taken:
typesafe_carla: warning: Actor.apply_control(VehicleControl) without as_vehicle() is a Python-API compatibility path that is not statically checked; call as_vehicle() first (...)
TYPESAFE_CARLA_COMPAT_WARNINGS=0 silences both, e.g. while porting a large
script. A program that uses only the statically checked style gets no
warnings at all.
Strict mode turns every compatibility path into a compile error; the last line of the trace is the line using it:
uv run typesafe-codon --strict run main.py # or: build
TYPESAFE_CARLA_STRICT=1 uv run typesafe-codon run main.py
_strict.codon:26 (9-108): error: strict mode: call as_vehicle() first (Actor.apply_control is a Python-API compatibility shortcut)
├─ _actor_compat.codon:46 (9-24): error: during the realization of compat_shortcut(...)
╰─ main.py:7 (1-52): error: during the realization of apply_control(self: Actor, control: VehicleControl, S: Actor)
Enable it in CI for new projects (TYPESAFE_CARLA_STRICT=1), so code stays in
the statically checked style. Strict mode only adds errors: a program that
compiles in strict mode behaves the same without it. Some things are checked
in every mode, strict or not:
- a typed actor never gets another kind's methods (
vehicle.listen()does not compile); - argument types are always checked (
apply_control(carla.Transform())does not compile).
Differences from the CARLA Python API
Most code ports by changing import carla to import typesafe_carla as carla.
Keyword arguments use the official parameter names (issue #41), so calls
with keywords port too: TrafficManager's per-vehicle methods take actor
(and perc or percentage, as each official method does),
set_random_device_seed(value), set_synchronous_mode(mode_switch),
TrafficLight.set_green_time(green_time) & co., ActorList.find(id),
Vector3D's methods vector, Transform.transform_vector(in_point). Where
the official binding leaves the first argument positional-only and gives
its name to the next one, so do these: BoundingBox.contains(world_point, point) (point is the Transform) and
Vehicle.set_wheel_steer_direction(wheel, wheel_location) (wheel_location
is the angle). The names used before issue #41 (vehicle=, percentage=
where the official name is perc, seed=, enabled= for
set_synchronous_mode, seconds=, other=, actor_id= for
ActorList.find, in_vector=, bbox_transform=, angle_in_deg=) are now
compile errors ("... is an invalid keyword argument").
Deliberate differences, all in favour of static checking:
-
Subclass methods on a plain
Actorare checked at run time. As in the Python API,world.get_actor(id).apply_control(...)works. The Vehicle, Walker, WalkerAIController, TrafficLight, TrafficSign and Sensor methods onActorconvert with the matchingas_*()and raiseActorTypeErrorwhen the actor is of another kind; argument types are still checked at compile time. Each such shortcut warns once at compile time (launcher) and at run time (TYPESAFE_CARLA_COMPAT_WARNINGS=0silences both);typesafe-codon --strictmakes them compile errors. The static path isas_vehicle()& co., which return a typedVehicle,Walker, ... Limits:Actor.get_control()returns aVehicleControl(a walker's needsas_walker().get_control()). Only a plainActorhas the shortcuts: on a typed subclass, another kind's method is a compile error in every mode (vehicle.listen(): "Vehicle has no method listen()"), as in the Python API, wherecarla.Vehiclehas nolisten. -
A
Vector3Ddoes not become aLocationon assignment to a variable. As in CARLA 0.10.0,Locationarithmetic gives aVector3D, and the Python API converts it back implicitly. API parameters and field setters do the same here, with a warning (see docs/usage.md):t.location = loc + offsetandt.location /= kstore aLocation, as in Python. A variable cannot convert, because Codon has no hook for it and a variable keeps one static type: a rebound local (loc = actor.get_location()followed byloc = loc + offsetin a loop, or a conditionalloc = loc + offset) does not compile ('Vector3D' does not match expected type 'Location'). Writeloc += offset(in place:locstays aLocation, as in Python) orloc = carla.Location(loc + offset). -
No
/=on aLocation. The Python API has no in-place/=, soloc /= krebindslocto theVector3Dloc / k. A variable keeps one static type here, so on aLocation,Velocity,AngularVelocityorAccelerationvariable it is a compile error ('Vector3D' does not match expected type 'Location'); writeloc *= 1.0 / k(in place) orv = loc / k. On aVector3DorVector2Dit works as in Python, and on a field (t.location /= k) the setter converts, as above. -
Vector arithmetic otherwise matches the Python API. Arithmetic across
Vector3D,Location,Velocity,AngularVelocity,AccelerationandVector2Dmatches the Python API (tests/compatibility/arithmetic_cases.py):+and-mix any of the Vector3D family and give aVector3D;*and/by a scalar give aVector3D(aVector2Don aVector2D), andk / visv / k, as LibCarla computes it;+=,-=and*=update the left operand in place and keep its type (t.location += vchangest). Mixing aVector2Dwith the Vector3D family, or any other type (loc + rotation), is a compile error. One difference: aboolscalar (v * True) does not compile. -
Value semantics. The Python API's vectors and transforms are C++ values, and typesafe_carla copies where it does: constructors and field setters store copies (
t.location = loc, thenloc += v, leavestalone; a setter accepts the other vector type with a warning, as the constructor does), and getter methods and read-only properties return copies (ActorSnapshot.get_velocity(),Light.location,Landmark.transform,IMUMeasurement.accelerometer, ...). Field getters return the stored value, as the Python API's do, sot.location.x = 1andt.location += vchanget. Lists are not covered: a list field (VehiclePhysicsControl.wheels,torque_curve, ...) is shared with the list it was set from. -
get_landmarks_of_type(distance, type): pass the type by position. Codon 0.19 cannot compile these methods with a parameter namedtype, so it islandmark_type(as inMap.get_all_landmarks_of_type). -
distancetakes either keyword. The Python API names the argumentvectorinVector3D.distanceandlocationinLocation.distance. Codon dispatches aLocationoverride virtually, which rejects keywords, so heredistanceacceptsvector=orlocation=(exactly one) on any vector, a superset of the Python API. -
Attribute values are typed.
ActorAttribute.as_int()raises when the attribute is not an int. Read values withas_int(),as_float(),as_bool(),as_color()oras_str():str(attribute)gives the Python API's text,ActorAttribute(id=number_of_wheels,type=int,value=4(const))(since issue #71; it used to give the raw value). -
Sensor callbacks run at dispatch points by default, not on CARLA's threads (see the callback thread below for the Python API's model).
sensor.listen(lambda data: ...)works, but the callback receives aSensorData(convert it withas_image()etc.; a callback typed(image: carla.Image)does not compile) and runs on the program's own thread: insideWorld.tick(),World.wait_for_tick(),Client.apply_batch_sync(..., do_tick=True)(after the server has answered),Client.apply_batch(..., do_tick=True)(right after sending: it does not wait), or when the program callscarla.dispatch_sensor_callbacks()(from one thread only). A blocking loop that never reaches one of these never sees its callbacks run. In synchronous mode a measurement of frame N may reach the client just aftertick()returned N; it is then delivered at the next dispatch point, so calldispatch_sensor_callbacks()(in a short wait loop if needed) when a frame's data is required right after its tick. An exception raised by a callback propagates out of the call that dispatched it, after the other sensors' callbacks have run.stop(),destroy()through any handle, or a batchDestroyActorunregisters the callback; stop or destroy callback sensors before the program exits. Callback mode queues without bound by default (listen(cb, queue_size=n)bounds it);poll()/wait_for_data()are for polling mode only. -
The callback thread (issue #86) is opt-in. In the Python API, sensor and
on_tickcallbacks run on LibCarla's streaming threads, so a program can tick and then block on its own queue (queue.get(True, 10.0)) while the data arrives. Here that pattern starves unless the program callscarla.start_callback_thread(). Then one background thread runs every callback as soon as its data is queued;tick()and the other dispatch points no longer run callbacks, anddispatch_callbacks()returns 0.carla.stop_callback_thread()returns to dispatch points (undelivered data is kept); the thread is also stopped at exit (a callback still running 2 s later is reported and abandoned). The switch is process-wide (the callback registry is), not per client. Unlike the Python API: callbacks run one at a time, on that one thread (not concurrently with each other); per stream in arrival order;stop()/destroy()/listen()wait for the callback in progress, so no callback of the stream starts after they return (do not make a callback wait for the main thread while it may be stopping a sensor: deadlock; and do not listen/stop a sensor from a callback while the main thread destroys it); an exception in a callback is printed to stderr and delivery goes on.Codon has no GIL, so callbacks race with the main thread. Every access to data shared with a callback, reads included (
len, indexing, iterating a list a callback appends to), must hold the samethreading.Lock; an unguardedList.appendfrom two threads corrupts memory. Codon 0.19 has noqueue.Queueorthreading.Condition, so a blocking get polls with a short sleep:class SensorQueue: lock: threading.Lock items: List[carla.SensorData] def __init__(self): self.lock = threading.Lock() self.items = [] def put(self, data: carla.SensorData): # sensor.listen(q.put) with self.lock: self.items.append(data) def get(self, timeout: float) -> carla.SensorData: deadline = time.time() + timeout while True: with self.lock: if len(self.items) > 0: return self.items.pop(0) if time.time() > deadline: raise carla.TimeoutError("no sensor data") time.sleep(0.001)
That is why it is off by default: existing programs whose callbacks append to unguarded lists stay correct.
TYPESAFE_CARLA_CALLBACK_THREAD=1starts the thread at the first callback registration (listen(callback),listen_to_gbuffer,on_tick) without changing the program: it makes an unchanged program multithreaded, so set it only for programs whose callbacks follow the rule above. An explicitstart_callback_thread()orstop_callback_thread()overrides it.A program (or wrapper) may instead run its own dispatcher thread:
carla.set_auto_dispatch(False), then loop onseen = carla.queue_signal_count(),carla.dispatch_callbacks(),carla.wait_queue_signal(seen, timeout); the registry is then locked as with the callback thread. A thread not created by Codon (e.g. a CPython thread) callscarla.attach_current_thread()first. pycarla, serialized by the GIL, passesset_auto_dispatch(False, lock=False): thenstop()does not wait for a callback already running, andstart_callback_thread()/TYPESAFE_CARLA_CALLBACK_THREADare refused or ignored. -
TrafficLightis not statically aTrafficSign. In the Python APIcarla.TrafficLightderives fromcarla.TrafficSign. Here both derive fromActor(see Codon limitation 9): aTrafficLighthastrigger_volumeitself,is_traffic_sign()is true for it, andlight.as_traffic_sign()converts it where aTrafficSignis expected (isinstance(light, carla.TrafficSign)is false). -
Actor skeleton queries need LibCarla newer than CARLA 0.10.0.
get_bone_names,get_bone_world_transforms,get_bone_relative_transforms,get_component_names,get_component_world_transform,get_component_relative_transform,get_socket_names,get_socket_world_transformsandget_socket_relative_transformsare in LibCarlaue5-devbut not in the0.10.0tag, and a CARLA 0.10.0 server does not implement their RPCs. Built against 0.10.0 they raiseCarlaError(the official 0.10.0 wheel has the methods, but the 0.10.0 server rejects them). Against aue5-devserver they work; an unknown component name, or bones of an actor without a skinned mesh, raiseCarlaErroras the server reports them. -
TextureColor.get/setcheck the pixel position (IndexError); the Python API reads or writes out of bounds. -
World.on_tickcallbacks also run at dispatch points, with the same rules as sensor callbacks (above): the callback receives aWorldSnapshot(a callback typed for anything else does not compile) on the program's own thread, not on LibCarla's.tick()/wait_for_tick()wait until the returned frame's snapshot is queued before dispatching it. -
apply_*_texture_to_objects([])raisesCarlaErrorfor an empty name list; the Python API sends a request that does nothing. -
WheelPhysicsControl.velocityis aVector3D. LibCarla and the Python API store it as aLocation; positions (location,old_location,center_of_mass) stayLocation. -
Float precision (issue #81). As in LibCarla and the Python API,
Vector3D,Location,Velocity,AngularVelocity,Acceleration,Vector2D,RotationandQuaternionstore float32: each field is rounded to float32 on construction, on assignment and for every arithmetic result, soLocation(0.1).xis0.10000000149011612,Location(16777217).xis16777216.0andVector3D(1e30) * 1e10isinf. The fields are Codonfloats holding float32 values; reading them costs nothing and the C ABI is unchanged.+ - * /(the scalar rounded to float32 first, as LibCarla'sfloatparameter),length,squared_length,dot,cross,distance*,make_unit_vectorandget_vector_angle, andQuaternion'slength,inverse,unit_quaternion,*and basis vectors, compute step by step in float32, as LibCarla, and give the Python API's results bit for bit (tests/compatibility/arithmetic_cases).Quaternion(rotation),Quaternion.rotator,Rotation.get_normalized,Transform.transformandtransform_vectoruse other formulas or double intermediates and round only the result, so they can differ from the Python API in the low bits, more for components near zero (issue #90). Double fields stay double, as in LibCarla:GeoLocation, the geo projections,GeoEllipsoid,GeoOffsetTransformandWorldSettings' time steps. Other value types withfloatfields in LibCarla (the controls, the physics controls,WeatherParameters, ...) still store the double you set: they become float32 when sent to the server, soget_control().throttleafter setting0.2is0.2000000029802322, and==compares them in float32. -
==/!=(issue #70) follow LibCarla'soperator==on every value type the Python API compares. Float fields compare exactly in float32, as LibCarla stores them:Location(0.1) == Location(0.1 + 1e-12)isTrue(both store the same float32), as isVehicleControl(throttle=0.1) == VehicleControl(throttle=0.1 + 1e-12)although the doubles kept there differ. Double fields (GeoLocation, the geo projections,GeoEllipsoid,GeoOffsetTransform, andWorldSettings'fixed_delta_secondsandmax_substep_delta_time) compare exactly. As in LibCarla, some types compare less than every field.Rotations are also equal when each pair of angles has|a| + |b| == 180, soRotation(90, 90, 90) == Rotation(-90, -90, -90).Colorignores alpha.TimestampandWorldSnapshotcompare the frame only.ActorAttributecompared with another attribute checks type and value, not the id. Compared with astrorColor, it reads itself as that type, raisingCarlaErroron a type mismatch (LibCarla'sBadAttributeCast). Compared with afloat, anintor abool, it reads itself as a float, as the Python API does: Boost.Python's float overload accepts any Python int, bools included. Soattr == 4on an int attribute andattr == Trueon a bool attribute raise in both, andfov_attr == 90isTrue. Writeattr.as_int() == 4,int(attr) == 4(Python API only) orattr.as_bool()instead.as_bool()accepts "true"/"false" in any case and raises on anything else, as LibCarla. Comparing unrelated types does not compile; the Python API answersFalse. -
Sensor data (issue #24).
raw_datais a property, as in the Python API (issue #78), onImage,LidarMeasurement,SemanticLidarMeasurement,RadarMeasurement,DVSEventArray,OpticalFlowImageandFakeImage. It returns aRawData, a read-only zero-copy view standing in for thememoryview(len, indexing, slicing with any step, iteration,in,==,hash,nbytes,readonly,tolist(),tobytes()); writing through it is a compile error, and it keeps the measurement's buffer alive.repr/strgive<memory of N bytes>where amemoryviewgives<memory at 0x...>. For aFakeImage,append/extendmove the bytes to a new buffer, so an earlier view keeps showing the bytes it was taken of (a Pythonmemoryviewwould block the resize instead);delshifts them in place, and an earlier view sees the shifted bytes.FakeImage(width, height, fov, data)copiesdata: changing the caller's list afterwards does not change the image. Codon has nobytes:bytes(m.raw_data)becomesm.raw_data.tobytes()(a copy in astr), and, as Codon'snp.frombuffertakes only astr,np.frombuffer(m.raw_data, dtype=np.uint8)becomesnp.frombuffer(m.raw_data.tobytes(), dtype=np.uint8). The zero-copy pointer israw_data_ptr()(aPtr[u8], valid while the measurement is alive), withraw_size()in bytes. Reading an element (image[i],radar[i], ...) gives a copy; assigning one (image[i] = carla.Color(...), issue #33) writes into LibCarla's buffer, as in the Python API, soraw_dataviews see it. A field that does not fit LibCarla's element type (a color component outside [0, 255], a DVSxbeyond uint16, a negativeobject_idx) raisesCarlaError, where the Python API raisesOverflowError/TypeError. Negative indices count from the end (the Python API rejects them).Image.save_to_diskwrites PNG only, as LibCarla does when built with PNG support only (CARLA's default build): any other extension is replaced by.png, missing directories are created, and the path written is returned. Depth and LogarithmicDepth give 8-bit gray, the others 8-bit RGBA. The file is written uncompressed (stored deflate blocks, about 4 bytes per pixel), because LibCarla'sImageIOneeds libpng's headers, which LibCarla's build does not export to its users. The converters reproduce LibCarla'sColorConverter(with Boost.GIL's rounding) and use LibCarla's CityScapes palette (29 tags in 0.10.0, 30 in ue5-dev). One PNG IDAT chunk holds the image, which limits it to about 0.5 Gpx.- Paths of
save_to_disk(images and point clouds) go through LibCarla'sFileSystem::ValidateFilePath. A bare file name ("out.png") raisesCarlaErrorwith CARLA 0.10.0 and ue5-dev alike, as it raises in the official API: its parent path is empty, which libstdc++'screate_directories(0.10.0) andabsolute(ue5-dev) reject. Write"./out.png"or give a directory. LaneInvasionEvent.actoralways raisesCarlaError("trying to operate on a destroyed actor ..."), as it raisesRuntimeErrorin the Python API: lane invasion is computed on the client, and LibCarla (0.10.0 and ue5-dev) never gives these events an episode to resolve the actor in. The actor is the lane-invasion sensor's parent.LaneInvasionSensor,ServerSideSensorandClientSideSensorare the same type asSensor(issue #33). In the Python API they are subclasses.OpticalFlowImage.get_color_coded_flow()returns aFakeImage(the Python API's name) withraw_data,pixel(x, y)and, as the Python API's byte vector, indexing, assignment,del, iteration,in,appendandextendon single bytes (issue #33; slices are not supported).- DVS events are packed 13-byte records in LibCarla;
DVSEventArrayreads them in place, andto_image()/to_array*()are computed in Codon from that view (same results as LibCarla'sToImage/ToArray*). - V2X depends on the LibCarla version (issue #42).
Sensor.send,SensorData.as_cam_event()/as_custom_v2x_event()and theCAMEvent/CAMMessage/CustomV2XEvent/CustomV2XDatatypes need LibCarla ue5-dev, which hasServerSideSensor::Sendand the V2X measurement types. Built against LibCarla 0.10.0, which has neither, they compile but raiseCarlaError("... is not available in LibCarla 0.10.0"); no measurement is everSensorDataType.CAM/CustomV2X. A CARLA 0.10.0 server has nosensor.other.v2x*blueprints at all.CustomV2XBytesis a plain value and works with both. CAMMessage.get()returns a typedCAM, where the Python API returns a nested dict (the dict keys work too; see below). It holds the same content: the header (ItsPduHeader), the generation time, the station type, the reference position (with the altitude, which the Python API drops), and the containers of LibCarla'sLibITS.hasOptionals:basic_vehicle_container_high_frequency(heading, speed, ... asValueConfidence; its optional fields asOptional),protected_communication_zones_rsu(a road-side unit'sList[ProtectedCommunicationZone]) andbasic_vehicle_container_low_frequency(vehicle role, exterior lights,List[PathPoint]). Field names are LibITS's in snake_case and values are LibCarla's raw ETSI codes and units (latitude in 0.1 microdegrees, speed in cm/s, ...), where the Python API turns the enumerations into display strings ("Passenger Car", "CAM"). This is the whole CAM that LibCarla carries; there is no further ITS tree (DENM etc.) to bind. The power isCAMMessage.power, not part ofget(). An empty path history is[](the Python API givesNone). As the Python API builds a new dict per call,get()returns a new snapshot each time: changing one result (orpower) afterwards does not affect earlier or laterget()results.CustomV2XData.get()likewise returns aCustomV2XMessage(header,data:CustomV2XBytes).- The Python API's dict keys work on the typed results (issue #85), so
event[0].get()["Message"]["Message"]["DataSize"](upstreamsmoke/test_v2x.py) ports unchanged. Each literal key is resolved at compile time and has its own static type, mirroring the dict ofPythonAPI/carla/src/V2XData.cpplevel by level:get()has"Power"and"Message";["Message"]has"Header"("Protocol Version","Message ID","Station ID") and"Message": for a custom message"DataSize","MaxDataSize","Bytes", for a CAM"Generation Delta Time"and"CAM Parameters"with the Python API's containers and key names (its spelling"Position Confidence Eliipse"and"Performance class"included). As in the dict, enumerations are the display strings ("CUSTOM","Passenger Car","Unavailable"), a value the dict holds asNoneis anOptional("Acceleration Control", an empty"Path History"), and a key the dict leaves out (the container that is not present, an absent"Expiry Time") raisesKeyError."Bytes"is astrof the raw bytes (Codon'sb"..."literal is astrtoo, sopayload["Bytes"] == b"hello v2x"compiles and holds). An unknown key ("Altitude", which the dict lacks) is a compile error listing the dict's keys, and so is a computed (non-literal) key: use the typed attributes for those. Check aNone-valued key withis Nonebefore indexing into it:hf["Steering Wheel Angle"]["Value"]compiles (Codon unwraps theOptionalimplicitly) but fails at run time when the value is absent. The levels are views of oneget()snapshot (no copy). This is statically checked, so it is not a compatibility path and does not warn. CustomV2XBytes.get_bytes()/set_bytes()useList[int](Codon has nobytes);set_bytesrejects values outside [0, 255] withCarlaError.data_sizecan be set within [0, 100] only (the Python API accepts any uint8 and then reads past the 100-byte buffer).CustomV2XBytes.get()returns the object itself, which takes the dict's"DataSize"/"MaxDataSize"/"Bytes"keys.Sensor.sendalso takes astr(sent asset_stringwould store it). As in LibCarla, sending through a server-side sensor that is notsensor.other.v2x_customonly logs a warning; the client-side lane-invasion sensor raisesActorTypeError.- The mock backend mirrors ue5-dev's V2X sensors with synthetic messages:
a CAM from each
sensor.other.v2xevery tick (everygen_cam_minwithfixed_rate), built from the parent vehicle's state (a sensor without a vehicle or walker parent is a road-side unit), and custom messages delivered at the next tick to the othersensor.other.v2x_customsensors on the samechannel_id. Receivers withinfilter_distanceget everything at the transmit power (no path-loss model). - G-buffer textures (issue #33).
Sensor.listen_to_gbuffer(gbuffer_id, callback)works likelisten(callback): the callback receives aSensorData(as_image()) at the dispatch points, on the program's thread. Only RGB cameras serve G-buffers: another sensor raisesCarlaError(LibCarla logs a warning and delivers nothing). As in LibCarla,is_listeningbecomesTrue, andstop()leaves the G-buffer streams running;stop_gbuffer(id),destroy()or a batchDestroyActorends them (they are stopped before the actor is destroyed: LibCarla cannot unsubscribe a destroyed actor's G-buffer stream, which would then reconnect forever). An id outsideGBufferTextureIDraisesCarlaError(LibCarla aborts the process). G-buffer streams need a server that serves them: a CARLA 0.10.0 server does not, and rejects the subscription, solisten_to_gbufferraisesCarlaError("the server rejected the G-buffer subscription ...") and nothing is registered. With the official 0.10.0 Python API the same call aborts the whole process (an uncaughtstd::exceptionin LibCarla). Do not call it against a 0.10.0 server: the subscription is rejected only after the client timeout, and the server process then crashes and restarts, dropping every client's world.get_server_version()cannot tell such a server apart (ue5-dev servers also report "0.10.0"), so call it only against a server known to stream G-buffers. The integration test runs this check only withTSC_TEST_GBUFFER=1. - ROS2 (issue #33).
enable_for_ros()/disable_for_ros()/is_enabled_for_ros()call LibCarla'sServerSideSensor; a server built without ROS2 accepts the requests and reportsFalse. - The
ServerSideSensormethods raiseActorTypeErroron a lane-invasion sensor, which LibCarla computes on the client (in the Python API, acarla.ClientSideSensorhas no such methods).
Not a difference: lookups that can miss return None, as in Python.
World.get_actor, World.try_spawn_actor, ActorList.find,
WorldSnapshot.find, Map.get_waypoint, Waypoint.get_left_lane /
get_right_lane / get_junction, Vehicle.get_traffic_light and
World.get_random_location_from_navigation return None in the same cases as
the official API: an unknown actor id, an occupied spawn point, no lane at
the location or beyond the outermost lane, no junction, no traffic light, no
navigation mesh. (Sensor.poll, which has no Python counterpart, returns
None when the queue is empty.) The tests check these cases against a real
server, except the navigation-mesh case, which only the mock exercises.
get_actor(id) of an actor destroyed in the same episode still returns an
Actor: LibCarla caches actors on the client and never evicts them, and the
official API does the same. Use the world snapshot to tell whether an actor
still exists, not is_alive: a real server can report True for such an
actor, while the mock backend reports False.
Python-style code works without changes in most cases: if world.get_actor(id) is None,
if wp:, and using the value directly when it is known to exist
(world.try_spawn_actor(bp, t).destroy(), m.get_waypoint(loc).next(2.0)).
The exception is a chained lookup passed into annotated code, which needs
an explicit unwrap() (see Codon limitation 5).
The return type is Optional[T], which only adds static information: the
checker knows what the value is when it is not None, so
actor.set_transform(m.get_waypoint(loc)) is a compile error. Two details
differ at run time:
- Using a
Nonevalue raisesValueError(optional unpack failed: expected Actor, got None) where Python raisesAttributeError('NoneType' object has no attribute ...). - For an id outside the uint32 range (e.g.
-1),get_actorand thefindlookups returnNone; the official API raisesOverflowError.
str() gives the official text, field for field (issue #71):
Transform(Location(x=1.000000, y=2.000000, z=3.000000), Rotation(...)),
Actor(id=24, type=vehicle.tesla.model3) for every actor class,
ActorAttribute(id=number_of_wheels,type=int,value=4(const)), ... Floats
are printed as the binding does, with std::to_string (%f, after rounding
float fields to float32) or std::ostream (%g), by a formatter that
matches glibc exactly (-0.000000, -nan, half-to-even ties). repr() is
the same text, where Python gives <carla.Location object at 0x...>. Three
cases differ:
WorldSettingswithfixed_delta_secondsunset printsfixed_delta_seconds=None; the officialstr()raisesRuntimeError: bad_optional_access.CollisionEventandObstacleDetectionEventprintother_actor=Actor(id=.., type=..); the official binding streams the actor's shared pointer, i.e. a memory address.- Classes the official module prints with Python's default repr
(
SensorData,CAMEvent,CustomV2XEvent,Junction,Landmark, ...) keep a descriptive typesafe_carla repr.
Codon limitations found while building this
These affect how the design's guarantees should be read:
- Codon only type-checks functions that are called. A function nobody
calls is not checked, even with full annotations, so a downstream library
is checked where an application uses it, not on its own. Every program in
tests/compilecalls its functions so the tests really exercise the checker. The programs are compiled, never run. - Exceptions do not form a catchable hierarchy. User exceptions must
derive
Static[Exception], andexcept CarlaErrordoes not catchTimeoutError. Catch the specific classes. Each one carries the native status in.status. __del__is not registered automatically in Codon 0.19.3.class_allocpasses an untyped pointer toregister_finalizer, so finalizers never run._ffi.Handleregisters its own, which is what releases native handles.tests/unit/test_ownership.codonchecks this.-Ddefines take integer values only, so the library path can't be baked in at compile time. It comes fromTYPESAFE_CARLA_LIBat run time, with the RPATH as the fallback.Optional[T]is implicitly unwrapped. Codon accepts anOptional[Waypoint]where aWaypointis expected and raises at run time if it isNone, so forgetting theis Nonecheck afterget_actor,get_left_laneand similar calls is not a compile error. Codon does not narrow types after anis not Nonecheck either. Implicit unwrapping also has a gap: the result of anOptional-returning method called on anOptionalvalue (left = wp.get_left_lane()wherewp = m.get_waypoint(loc)) cannot go where a non-Optionaltype is annotated (a typed parameter,x: Waypoint = ..., a-> Waypointreturn): Codon reports'Waypoint' does not match expected type 'Optional[Waypoint]', even after anis not Nonecheck. Unannotated code is not affected. Unwrap explicitly with Codon'sunwrap():unwrap(wp).get_left_lane()orunwrap(wp.get_left_lane()).- Subclass values do not upcast inside
Optional,isinstanceis exact, and arguments typed late bind badly.Locationderives fromVector3D, but Codon rejects anOptional[Location]for anOptional[Vector3D]parameter, andisinstance(location, Vector3D)isFalse. An argument reached through an implicitly unwrappedOptionalcan be bound to aVector3Dparameter before Codon knows that it is aLocation. The vector and location parameters of the API (and ofVector3D's methods) are therefore generic and checked statically. This is also what lets aLocationpassed as aVector3D, or aVector3Dpassed as aLocation(both implicit in the Python API), warn, or fail in strict mode. Other types still fail with'Rotation' does not match expected type 'Vector3D'. -Ddefines are visible only in the main file, not in imported modules. The launcher passes the strict-mode setting to the library through a generated module (_tsc_build_config) instead.- Float format specifiers (
f"{x:.6f}") need an installeden_USlocale in Codon 0.19.3, sostr()formats floats with its own%f/%gimplementation (typesafe_carla/_fmt.codon). - Class hierarchies two levels deep are miscompiled. With
class B(A)andclass C(B), a methodCinherits fromBreadsA's fields at the wrong offset (aC(3)reportsx == 0throughB's methods), andsuper().__init__()chains through both levels crash. The API keeps every class one level belowActor:TrafficLightandTrafficSignare siblings (see Differences).
License
typesafe_carla is released under the MIT License. LibCarla (MIT)
is linked into the native library and its license is shipped as
_native/LICENSE.CARLA. LibCarla brings in more third-party code, also
linked statically:
- Boost (BSL-1.0);
- rpclib (MIT), with its bundled asio, msgpack-c, cppformat and optional-lite;
- RecastNavigation (Zlib), libpng (libpng-2.0 AND Libpng) and zlib (Zlib);
- LibCarla's vendored pugixml, odrSpiral (Apache-2.0), moodycamel ConcurrentQueue, Fast-Quadric-Mesh-Simplification and MeshReconstruction;
- the other MIT copyright holders named in LibCarla's own sources (e.g. the DVS sensor, GNSS measurement and vehicle physics files).
Their notices are shipped as _native/THIRD_PARTY_NOTICES. The build collects
them from the fetched sources (cmake/ThirdPartyNotices.cmake),
with each component's version, license and source path. Configuring fails if
a license text is missing, or if LibCarla links a library (directly or
transitively) or vendors third-party code the notices do not cover. Codon (Apache-2.0) is redistributed by typesafe-carla-toolchain with
its license.
Metadata
Release files for typesafe-carla 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| typesafe_carla-0.2.0.tar.gz | 654.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| typesafe_carla-0.2.0-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl | Python 3 | none | Linux glibc 2.28+ x86-64, Linux glibc 2.27+ x86-64 | Details |
Total release size: 15.0 MB
Release files / typesafe_carla-0.2.0.tar.gz
| Download URL | typesafe_carla-0.2.0.tar.gz |
|---|---|
| Size | 654.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e4f5ae0fa808a4f23774cf6a23d9ad3fb64cea9c6f0fd6c5b851cc8e08fcae8d
|
|
BLAKE2b-256 checksum How to use checksums |
1835734cc6cac99c4035051d418806149727b8cca85f23930f82d09e8513f5f9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.
Transparency logRelease files / typesafe_carla-0.2.0-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | typesafe_carla-0.2.0-py3-none-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 14.3 MB |
| Tags | Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
5356f3a069e25036ef9ae3300af4c7a1bc95175a5391c5da7006bdccb1c35d0f
|
|
BLAKE2b-256 checksum How to use checksums |
cedac7e142b37a1e955e259a5f26a929a3a1151d53589b4bebe2c75bf33f1e25
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.
Transparency log