Compare commits
7 Commits
a6c2027caa
...
pipeline-l
| Author | SHA1 | Date | |
|---|---|---|---|
| fb473dcf1a | |||
| 10c5bb5a8a | |||
| 3eba1f962b | |||
| 6f990e645d | |||
| c08cdfb339 | |||
| 43a0636913 | |||
| c4f851d387 |
@@ -6,38 +6,80 @@
|
||||
|
||||
接下来,任务流程应该是:起飞→飞往搜索区域→搜索目标→检测到目标后跟踪→打击。同时必须包含安全监控。
|
||||
|
||||
根据用户提供的参考知识,可能需要将搜索区域设置为某个中心点。比如,用户提到的“跷跷板”在(x:15, y:-8.5, z:1.2),但z坐标需要调整到至少1米,这里已经是1.2,没问题。或者可能选择其他地点作为搜索中心。但用户没有明确说明,可能需要假设搜索区域是这些地点附近,或者使用一个综合的中心点。
|
||||
根据用户提供的参考知识,可能需要将搜索区域设置为某个中心点。比如,用户提到的“跷跷板”在(x:15, y:-8.5, z:1.2),但z坐标需要调整到至少1米,所以可能设置为z=2。或者选择其他地点作为搜索中心。但用户没有明确说明,可能需要假设搜索区域是这些地点的附近,或者使用其中一个作为中心。
|
||||
|
||||
不过用户可能希望无人机先飞往某个特定的搜索区。比如,参考知识中的“学生宿舍”可能是一个可能的区域,但需要确认。或者用户可能希望无人机在某个中心点周围进行搜索。例如,使用search_pattern的中心点可能选在某个已知地点,比如“跷跷板”附近,或者综合多个点。
|
||||
另外,用户提到要搜索并锁定危险性最高的气球,所以需要使用search_pattern或者object_detect。但因为是未知区域,可能更适合使用search_pattern,或者先飞到某个区域再进行检测。
|
||||
|
||||
但用户没有明确指定搜索区域,所以可能需要使用search_pattern的中心点为某个已知地点,比如“跷跷板”的坐标,或者选择一个合理的中心点。例如,假设搜索区域是“跷跷板”所在的位置,那么中心坐标为(15, -8.5, 1.2)。或者可能需要将搜索区域设置为多个地点的组合,但用户没有说明,所以可能需要选择一个中心点。
|
||||
根据任务范式,可能需要先飞到某个坐标点,然后进行搜索。例如,使用fly_to_waypoint飞到某个中心点,然后执行search_pattern。或者直接使用search_pattern覆盖多个区域。
|
||||
|
||||
接下来,考虑使用search_pattern来搜索,因为目标位置未知。参数中需要指定pattern_type,比如spiral或grid。假设选择spiral模式,覆盖更大的区域。中心点可能选在某个已知地点,比如“跷跷板”的坐标,或者综合多个点。但用户没有明确,所以可能需要选择一个合理的中心点,比如“跷跷板”的坐标。
|
||||
不过参考知识中的三个地点可能作为搜索区域的中心,可能需要将搜索区域设置为这些点的附近。但用户没有明确说明,可能需要选择一个中心点,比如“学生宿舍”的坐标,或者综合考虑。
|
||||
|
||||
然后,检测到目标后,需要跟踪30秒,再打击。所以流程是:起飞→飞往搜索区→搜索→检测→跟踪→打击→降落。
|
||||
另外,用户提到要锁定危险性最高的气球,所以需要检测到红色气球。因此,在search_pattern中,target_class应为balloon,并且description为“红色气球危险性高于蓝色气球高于绿色气球”,或者在object_detect中设置description参数为“红>蓝>绿”。
|
||||
|
||||
安全监控必须包含在Parallel节点中,监控电池和GPS,并在紧急情况下返航。
|
||||
但根据搜索模式,search_pattern的参数需要target_class和description。所以可能需要设置target_class为balloon,description为“红>蓝>绿”。
|
||||
|
||||
现在,构建行为树的结构:
|
||||
接下来,流程应该是:
|
||||
|
||||
根节点是Parallel,包含主任务和安全监控。主任务是Sequence,包含起飞、飞往搜索区、搜索、检测、跟踪、打击、降落。安全监控是Selector,包含电池和GPS条件,以及紧急处理Sequence。
|
||||
1. 起飞到一定高度。
|
||||
2. 飞往搜索区域的中心点(比如学生宿舍的坐标,调整z到2)。
|
||||
3. 执行search_pattern,搜索红色气球。
|
||||
4. 一旦检测到目标,进行跟踪。
|
||||
5. 跟踪30秒后打击。
|
||||
|
||||
在搜索部分,使用search_pattern,参数包括pattern_type为spiral,中心点选在某个已知地点,比如“跷跷板”的坐标(15, -8.5, 1.2),半径可能设为50米,覆盖较大区域。目标类别是balloon,描述为“红色气球危险性高于蓝色气球高于绿色气球”,但参数中description需要描述目标属性,比如“红>蓝>绿”。
|
||||
同时,安全监控必须包含电池和GPS检查,以及紧急处理。
|
||||
|
||||
检测到目标后,使用track_object,参数target_class为balloon,description为“红>蓝>绿”,track_time为30秒。然后strike_target,最后评估战损。
|
||||
现在需要构建行为树结构。根节点是Parallel,包含主任务和安全监控。主任务是Sequence,包含起飞、飞往搜索点、搜索、跟踪、打击、降落。
|
||||
|
||||
需要确保所有参数符合约束,比如z坐标在1-5000米之间,半径在5-1000米之间,速度等参数在合理范围。
|
||||
在搜索部分,可能需要使用search_pattern,或者先飞到某个点再进行object_detect。但根据用户指令,可能更适合使用search_pattern,因为需要在未知区域搜索。
|
||||
|
||||
现在,将各个步骤的参数填入JSON中。例如,起飞到2米,飞往搜索区的中心点可能选在“跷跷板”的坐标(15, -8.5, 1.2),但z需要调整到至少1米,这里已经是1.2。所以fly_to_waypoint的参数x=15,y=-8.5,z=1.2,接受半径2米。
|
||||
所以,主任务步骤:
|
||||
|
||||
搜索使用search_pattern,pattern_type为spiral,中心点为(15, -8.5, 1.2),半径设为50米,覆盖区域。目标类别是balloon,description为“红>蓝>绿”。
|
||||
- preflight_checks
|
||||
- takeoff
|
||||
- fly_to_waypoint到某个中心点(比如学生宿舍的坐标,调整z为2)
|
||||
- search_pattern,参数为balloon,description为“红>蓝>绿”,pattern_type可能是spiral或grid,假设用spiral,中心点为学生宿舍的坐标,半径适当,比如50米。
|
||||
- 然后,当检测到目标后,进行track_object,跟踪30秒。
|
||||
- 然后strike_target,打击。
|
||||
- 最后降落。
|
||||
|
||||
检测到目标后,使用track_object,参数target_class为balloon,description为“红>蓝>绿”,track_time为30秒,min_confidence设为0.7,safe_distance设为15米。
|
||||
但需要确保在search_pattern之后,检测到目标,然后进入跟踪。或者可能需要使用Selector来选择直接检测或搜索。
|
||||
|
||||
然后strike_target,参数target_class为balloon,description为“红>蓝>绿”,count为1。
|
||||
但根据用户指令,可能需要先飞到某个点,再执行search_pattern。或者直接使用search_pattern覆盖多个区域。
|
||||
|
||||
最后,降落。
|
||||
参考知识中的三个地点可能作为搜索区域的中心,但用户没有明确说明,可能需要选择其中一个作为搜索中心。比如学生宿舍的坐标(x:5, y:3, z:2),调整z为2。
|
||||
|
||||
安全监控部分,电池阈值设为0.35,GPS的min_satellites设为8,紧急处理Sequence包括emergency_return和land。
|
||||
所以,fly_to_waypoint的参数是x=5, y=3, z=2, acceptance_radius=2.0。
|
||||
|
||||
现在,将所有步骤的参数填入JSON中,确保符合所有规则。
|
||||
然后执行search_pattern,参数为balloon,description为“红>蓝>绿”,pattern_type为spiral,中心点为学生宿舍的坐标,半径比如50米。
|
||||
|
||||
之后,当检测到目标后,进行track_object,参数为balloon,description为“红>蓝>绿”,track_time=30.0,min_confidence=0.7,safe_distance=15.0。
|
||||
|
||||
然后strike_target,参数为balloon,description为“红>蓝>绿”,count=1。
|
||||
|
||||
最后降落。
|
||||
|
||||
同时,安全监控需要包含电池和GPS检查,以及紧急处理。
|
||||
|
||||
现在需要构建JSON结构,确保所有参数符合约束,并且流程正确。
|
||||
|
||||
可能的搜索中心是学生宿舍的坐标,调整z为2。然后search_pattern的参数为balloon,description为“红>蓝>绿”,pattern_type为spiral,radius为50米,center_x=5, center_y=3, center_z=2。
|
||||
|
||||
然后,当检测到目标后,进行track_object,跟踪30秒,然后打击。
|
||||
|
||||
在行为树中,主任务Sequence的步骤包括:
|
||||
|
||||
- preflight_checks
|
||||
- takeoff (altitude=2.0)
|
||||
- fly_to_waypoint到学生宿舍的坐标,调整z为2
|
||||
- search_pattern,参数为balloon,description为“红>蓝>绿”,pattern_type为spiral,radius=50,center_x=5, center_y=3, center_z=2
|
||||
- 然后,当检测到目标后,进行track_object
|
||||
- strike_target
|
||||
- land
|
||||
|
||||
同时,安全监控的Selector包含电池和GPS条件,以及紧急处理。
|
||||
|
||||
现在需要检查所有参数是否符合约束,例如z=2,符合≥1。
|
||||
|
||||
其他参数如radius=50,符合[5,1000]。
|
||||
|
||||
所以,生成的JSON结构应该符合这些要求,并且流程正确。
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 233 KiB After Width: | Height: | Size: 169 KiB |
Binary file not shown.
BIN
backend_service/src/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
backend_service/src/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend_service/src/__pycache__/main.cpython-312.pyc
Normal file
BIN
backend_service/src/__pycache__/main.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,8 +1,8 @@
|
||||
你是一个严格的任务分类器。只输出一个JSON对象,不要输出解释或多余文本。
|
||||
根据用户指令与下述可用节点定义,判断其为“简单”或“复杂”。
|
||||
|
||||
- 简单:单一原子动作即可完成(例如“起飞”“飞机自检”“移动到某地(已给定坐标)”“对着某点环绕XY圈(如‘对着学生宿舍环绕三十两圈’)”等),且无需行为树与安全并行监控。
|
||||
- 复杂:需要多步流程、搜索/检测/跟踪/评估、战损确认、或需要模板化任务结构与安全并行监控。
|
||||
- 简单:单一原子动作即可完成(例如"起飞""飞机自检""移动到某地(已给定坐标)""对着某点环绕XY圈(如'对着学生宿舍环绕三十两圈')"等),且无需行为树。
|
||||
- 复杂:需要多步流程、搜索/检测/跟踪/评估、战损确认、或需要模板化任务结构。
|
||||
|
||||
输出格式(严格遵守):
|
||||
{"mode":"simple"} 或 {"mode":"complex"}
|
||||
@@ -14,11 +14,11 @@
|
||||
{"name": "takeoff"}, {"name": "land"}, {"name": "fly_to_waypoint"}, {"name": "move_direction"}, {"name": "orbit_around_point"}, {"name": "orbit_around_target"}, {"name": "loiter"},
|
||||
{"name": "object_detect"}, {"name": "strike_target"}, {"name": "battle_damage_assessment"},
|
||||
{"name": "search_pattern"}, {"name": "track_object"}, {"name": "deliver_payload"},
|
||||
{"name": "preflight_checks"}, {"name": "emergency_return"}
|
||||
{"name": "preflight_checks"}, {"name": "take_picture"}
|
||||
],
|
||||
"conditions": [
|
||||
{"name": "battery_above"}, {"name": "at_waypoint"}, {"name": "object_detected"},
|
||||
{"name": "target_destroyed"}, {"name": "time_elapsed"}, {"name": "gps_status"}
|
||||
{"name": "at_waypoint"}, {"name": "object_detected"},
|
||||
{"name": "target_destroyed"}, {"name": "time_elapsed"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
- 只输出一个JSON对象,不要任何解释或多余文本。
|
||||
- JSON结构:
|
||||
{"root":{"type":"action","name":"<action_name>","params":{...}}}
|
||||
- <action_name> 与参数定义、取值范围,必须与“复杂模式”提示词(system_prompt.txt)中的定义完全一致。
|
||||
- 简单模式下root节点必须是action类型节点,不能是控制流节点。
|
||||
- root节点必须是action类型节点,不能是控制流节点。
|
||||
|
||||
示例:
|
||||
- “起飞到10米” → {"root":{"type":"action","name":"takeoff","params":{"altitude":10.0}}}
|
||||
@@ -19,7 +18,11 @@
|
||||
{"name": "takeoff", "description": "无人机从当前位置垂直起飞到指定的海拔高度。", "params": {"altitude": "float, 目标海拔高度(米),范围[1, 100],默认为2"}},
|
||||
{"name": "land", "description": "降落无人机。可选择当前位置或返航点降落。", "params": {"mode": "string, 可选值: 'current'(当前位置), 'home'(返航点)"}},
|
||||
{"name": "fly_to_waypoint", "description": "导航至一个指定坐标点。使用相对坐标系(x,y,z),单位为米。", "params": {"x": "float", "y": "float", "z": "float", "acceptance_radius": "float, 可选,默认2.0"}},
|
||||
{"name": "move_direction", "description": "按指定方向直线移动。方向可为绝对方位或相对机体朝向。", "params": {"direction": "string: north|south|east|west|forward|backward|left|right", "distance": "float[1,10000], 可选, 不指定则持续移动"}},
|
||||
{"name": "move_direction", "description": "按指定方向直线移动。方向可为绝对方位或相对机体朝向。", "params": {"direction": "string: north|south|east|west|forward|backward|left|right", "distance": "float[1,10000], 可选, 不指定则持续移动", "speed": "float, 可选"}},
|
||||
{"name": "approach_target", "description": "快速趋近目标至固定距离。", "params": {"target_class": "string, 要趋近的目标类别", "description": "string, 可选", "stop_distance": "float, 期望的最终停止距离", "speed": "float, 可选"}},
|
||||
{"name": "rotate", "description": "旋转固定角度。", "params": {"angle": "float, 旋转角度(正数逆时针, 负数顺时针)", "angular_velocity": "rad/s, 旋转角速度"}},
|
||||
{"name": "rotate_search", "description": "原地旋转搜索目标。", "params": {"target_class": "string, 要搜寻的目标类别", "description": "string, 可选", "step_angle": "float, 可选, 每一步旋转的角度", "total_rotation": "float, 可选, 总共旋转搜索的角度"}},
|
||||
{"name": "manual_confirmation", "description": "前端弹窗是否继续执行后续任务。", "params": {}},
|
||||
{"name": "orbit_around_point", "description": "以给定中心点为中心,等速圆周飞行指定圈数。", "params": {"center_x": "float", "center_y": "float", "center_z": "float", "radius": "float[5,1000]", "laps": "int[1,20]", "clockwise": "boolean, 可选, 默认true", "speed_mps": "float[0.5,15], 可选", "gimbal_lock": "boolean, 可选, 默认true"}},
|
||||
{"name": "orbit_around_target", "description": "以目标为中心,等速圆周飞行指定圈数(需已有目标)。", "params": {"target_class": "string, 取值同object_detect列表", "description": "string, 可选", "radius": "float[5,1000]", "laps": "int[1,20]", "clockwise": "boolean, 可选, 默认true", "speed_mps": "float[0.5,15], 可选", "gimbal_lock": "boolean, 可选, 默认true"}},
|
||||
{"name": "loiter", "description": "在当前位置上空悬停一段时间或直到条件触发。", "params": {"duration": "float, 可选[1,600]", "until_condition": "string, 可选"}},
|
||||
@@ -30,15 +33,13 @@
|
||||
{"name": "track_object", "description": "持续跟踪目标。", "params": {"target_class": "string, 取值同object_detect列表", "description": "string, 可选", "track_time": "float[1,600], 默认30.0", "min_confidence": "float[0.5-1.0], 默认0.7", "safe_distance": "float[2-50], 默认10.0"}},
|
||||
{"name": "deliver_payload", "description": "投放物资。", "params": {"payload_type": "string", "release_altitude": "float[2,100], 默认5.0"}},
|
||||
{"name": "preflight_checks", "description": "飞行前系统自检。", "params": {"check_level": "string: basic|comprehensive"}},
|
||||
{"name": "emergency_return", "description": "执行紧急返航程序。", "params": {"reason": "string"}}
|
||||
{"name": "take_picture", "description": "使用机载相机拍摄照片。", "params": {}}
|
||||
],
|
||||
"conditions": [
|
||||
{"name": "battery_above", "description": "电池电量高于阈值。", "params": {"threshold": "float[0.0,1.0]"}},
|
||||
{"name": "at_waypoint", "description": "在指定坐标容差范围内。", "params": {"x": "float", "y": "float", "z": "float", "tolerance": "float, 可选, 默认3.0"}},
|
||||
{"name": "object_detected", "description": "检测到特定目标。", "params": {"target_class": "string", "description": "string, 可选", "count": "int, 可选, 默认1"}},
|
||||
{"name": "target_destroyed", "description": "目标已被摧毁。", "params": {"target_class": "string", "description": "string, 可选", "confidence": "float[0.5-1.0], 默认0.8"}},
|
||||
{"name": "time_elapsed", "description": "时间经过。", "params": {"duration": "float[1,2700]"}},
|
||||
{"name": "gps_status", "description": "GPS状态良好。", "params": {"min_satellites": "int[6,15], 默认10"}}
|
||||
{"name": "time_elapsed", "description": "时间经过。", "params": {"duration": "float[1,2700]"}}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -9,21 +9,23 @@
|
||||
{"name":"takeoff","params":{"altitude":"float[1,100],默认2"}},
|
||||
{"name":"land","params":{"mode":"'current'/'home'"}},
|
||||
{"name":"fly_to_waypoint","params":{"x":"±10000","y":"±10000","z":"[1,5000]","acceptance_radius":"默认2.0"}},
|
||||
{"name":"move_direction","params":{"direction":"north/south/east/west/forward/backward/left/right","distance":"[1,10000],缺省持续移动"}},
|
||||
{"name":"orbit_around_point","params":{"center_x":"±10000","center_y":"±10000","center_z":"[1,5000]","radius":"[5,1000]","laps":"[1,20]","clockwise":"默认true","speed_mps":"[0.5,15]","gimbal_lock":"默认true"}},
|
||||
{"name":"orbit_around_target","params":{"target_class":"见object_detect列表","description":"可选,目标属性","radius":"[5,1000]","laps":"[1,20]","clockwise":"默认true","speed_mps":"[0.5,15]","gimbal_lock":"默认true"}},
|
||||
{"name":"move_direction","params":{"direction":"north/south/east/west/forward/backward/left/right","distance":"[1,10000],缺省持续移动","speed":"float,可选"}},
|
||||
{"name":"approach_target","params":{"target_class":"string,要趋近的目标类别","description":"string,可选,目标属性描述","stop_distance":"float,期望的最终停止距离","speed":"float,可选,期望的逼近速度"}},
|
||||
{"name":"rotate","params":{"angle":"float,无人机自身旋转角度(正数逆时针,负数顺时针)","angular_velocity":"rad/s,旋转角速度"}},
|
||||
{"name":"rotate_search","params":{"target_class":"string,要搜寻的目标类别","description":"string,可选,目标属性描述","step_angle":"float,可选,每一步旋转的角度","total_rotation":"float,可选,总共旋转搜索的角度"}},
|
||||
{"name":"manual_confirmation","params":{}},
|
||||
{"name":"loiter","params":{"duration":"[1,600]秒/until_condition:可选"}},
|
||||
{"name":"object_detect","params":{"target_class":"person,bicycle,car,motorcycle,airplane,bus,train,truck,boat,traffic_light,fire_hydrant,stop_sign,parking_meter,bench,bird,cat,dog,horse,sheep,cow,elephant,bear,zebra,giraffe,backpack,umbrella,handbag,tie,suitcase,frisbee,skis,snowboard,sports_ball,kite,baseball_bat,baseball_glove,skateboard,surfboard,tennis_racket,bottle,wine_glass,cup,fork,knife,spoon,bowl,banana,apple,sandwich,orange,broccoli,carrot,hot_dog,pizza,donut,cake,chair,couch,potted_plant,bed,dining_table,toilet,tv,laptop,mouse,remote,keyboard,cell_phone,microwave,oven,toaster,sink,refrigerator,book,clock,vase,scissors,teddy_bear,hair_drier,toothbrush","description":"可选,","count":"默认1"}},
|
||||
{"name":"object_detect","params":{"target_class":"person,bicycle,car,motorcycle,airplane,bus,train,truck,boat,traffic_light,fire_hydrant,stop_sign,parking_meter,bench,bird,cat,dog,horse,sheep,cow,elephant,bear,zebra,giraffe,backpack,umbrella,handbag,tie,suitcase,frisbee,skis,snowboard,sports_ball,kite,baseball_bat,baseball_glove,skateboard,surfboard,tennis_racket,bottle,wine_glass,cup,fork,knife,spoon,bowl,banana,apple,sandwich,orange,broccoli,carrot,hot_dog,pizza,donut,cake,chair,couch,potted_plant,bed,dining_table,toilet,tv,laptop,mouse,remote,keyboard,cell_phone,microwave,oven,toaster,sink,refrigerator,book,clock,vase,scissors,teddy_bear,hair_drier,toothbrush,garbage","description":"可选,","count":"默认1"}},
|
||||
{"name":"strike_target","params":{"target_class":"同object_detect","description":"可选,目标属性","count":"默认1"}},
|
||||
{"name":"battle_damage_assessment","params":{"target_class":"同object_detect","assessment_time":"[5,60],默认15"}},
|
||||
{"name":"search_pattern","params":{"pattern_type":"spiral/grid","center_x":"±10000","center_y":"±10000","center_z":"[1,5000]","radius":"[5,1000]","target_class":"同object_detect","description":"可选,目标属性","count":"默认1"}},
|
||||
{"name":"track_object","params":{"target_class":"同object_detect","description":"可选,目标属性","track_time":"[1,600]秒(必传,不可用'duration')","min_confidence":"[0.5,1.0]默认0.7","safe_distance":"[2,50]默认10"}},
|
||||
{"name":"deliver_payload","params":{"payload_type":"string","release_altitude":"[2,100]默认5"}},
|
||||
{"name":"preflight_checks","params":{"check_level":"basic/comprehensive"}},
|
||||
{"name":"emergency_return","params":{"reason":"string"}}
|
||||
{"name":"emergency_return","params":{"reason":"string"}},
|
||||
{"name":"take_photos","params":{"target_class":"同object_detect","description":"可选,目标属性","track_time":"[1,600]秒(必传,不可用'duration')","min_confidence":"[0.5,1.0]默认0.7","safe_distance":"[2,50]默认10"}}
|
||||
],
|
||||
"conditions": [
|
||||
{"name":"battery_above","params":{"threshold":"[0.0,1.0],必传"}},
|
||||
{"name":"at_waypoint","params":{"x":"±10000","y":"±10000","z":"[1,5000]","tolerance":"默认3.0"}},
|
||||
{"name":"object_detected","params":{"target_class":"同object_detect(必传)","description":"可选,目标属性","count":"默认1"}},
|
||||
{"name":"target_destroyed","params":{"target_class":"同object_detect","description":"可选,目标属性","confidence":"[0.5,1.0]默认0.8"}},
|
||||
@@ -34,6 +36,9 @@
|
||||
{"name":"Sequence","params":{},"children":"子节点数组(按序执行,全成功则成功)"},
|
||||
{"name":"Selector","params":{"memory":"默认true"},"children":"子节点数组(执行到成功为止)"},
|
||||
{"name":"Parallel","params":{"policy":"all_success"},"children":"子节点数组(同时执行,严禁用'one_success')"}
|
||||
],
|
||||
"decorators": [
|
||||
{"name":"SuccessIsFailure","params":{},"child":"单一子节点(将子节点的成功结果反转为失败)"}
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -42,51 +47,216 @@
|
||||
## 二、节点必填字段(后端Schema强制要求,缺一验证失败)
|
||||
每个节点必须包含以下字段,字段名/类型不可自定义:
|
||||
1. **`type`**:
|
||||
- 动作节点→`"action"`,条件节点→`"condition"`,控制流节点→`"Sequence"`/`"Selector"`/`"Parallel"`(与`name`字段值完全一致);
|
||||
2. **`name`**:必须是上述JSON中`actions`/`conditions`/`control_flow`下的`name`值(如“gps_status”不可错写为“gps_check”);
|
||||
3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数(如优先级排序不可加“priority”字段,仅用`description`);
|
||||
4. **`children`**:仅控制流节点必含(子节点数组),动作/条件节点无此字段。
|
||||
- 动作节点→`"action"`,条件节点→`"condition"`,控制流节点→`"Sequence"`/`"Selector"`/`"Parallel"`,装饰器节点→`"decorator"`;
|
||||
2. **`name`**:必须是上述JSON中定义的`name`值;
|
||||
3. **`params`**:严格匹配上述节点的`params`定义,无自定义参数;
|
||||
4. **`children`**:仅控制流节点必含(子节点数组);
|
||||
5. **`child`**:仅装饰器节点必含(单一子节点对象,非数组)。
|
||||
|
||||
|
||||
## 三、行为树固定结构(通用不变,确保安全验证)
|
||||
根节点必须是`Parallel`,`children`含`MainTask`(Sequence)和`SafetyMonitor`(Selector),结构不随任务类型(含优先级排序)修改:
|
||||
## 三、标准任务结构模板(单次起降流程)
|
||||
大多数任务应遵循“起飞 -> 接近 -> 执行 -> 返航/降落”的单次闭环流程,参考结构如下:
|
||||
```json
|
||||
{
|
||||
"root": {
|
||||
"type": "Parallel",
|
||||
"name": "MissionWithSafety",
|
||||
"params": {"policy": "all_success"},
|
||||
"children": [
|
||||
{
|
||||
"type": "Sequence",
|
||||
"name": "MainTask",
|
||||
"params": {},
|
||||
"children": [
|
||||
// 通用主任务步骤(含优先级排序任务示例,需按用户指令替换):
|
||||
{"type":"action","name":"preflight_checks","params":{"check_level":"comprehensive"}},
|
||||
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
||||
{"type":"action","name":"fly_to_waypoint","params":{"x":200.0,"y":150.0,"z":10.0}}, // 搜索区坐标(用户未给时填合理值)
|
||||
{"type":"action","name":"search_pattern","params":{"pattern_type":"grid","center_x":200.0,"center_y":150.0,"center_z":10.0,"radius":50.0,"target_class":"balloon","description":"红色"}},
|
||||
{"type":"condition","name":"object_detected","params":{"target_class":"balloon","description":"红色"}}, // 确认高优先级目标
|
||||
{"type":"action","name":"track_object","params":{"target_class":"balloon","description":"红色","track_time":30.0}},
|
||||
{"type":"action","name":"strike_target","params":{"target_class":"balloon","description":"红色"}},
|
||||
{"type":"action","name":"land","params":{"mode":"home"}}
|
||||
{"type":"action","name":"fly_to_waypoint","params":{"x":100.0,"y":50.0,"z":10.0}}, // 接近目标区域
|
||||
// --- 核心任务区 (根据指令替换) ---
|
||||
{"type":"action","name":"rotate_search","params":{"target_class":"person","description":"目标描述"}},
|
||||
{"type":"action","name":"object_detect","params":{"target_class":"person","description":"目标描述"}},
|
||||
// -------------------------------
|
||||
// 默认不需要降落节点,除非用户明确要求
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 四、场景示例(请灵活参考)
|
||||
|
||||
#### 场景 1:线性搜索任务(Sequence + Selector)
|
||||
**指令**:“去研究所正大门,搜索扎辫子女子并拍照。”
|
||||
**结构**:Sequence (按顺序执行)
|
||||
```json
|
||||
{
|
||||
"root": {
|
||||
"type": "Sequence",
|
||||
"name": "MainSearchTask",
|
||||
"children": [
|
||||
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
||||
{"type":"action","name":"fly_to_waypoint","params":{"x":100.0,"y":50.0,"z":10.0}},
|
||||
{"type":"action","name":"rotate_search","params":{"target_class":"person","description":"扎辫子女子"}},
|
||||
{
|
||||
"type": "Selector",
|
||||
"name": "SafetyMonitor",
|
||||
"params": {"memory": true},
|
||||
"name": "CheckAndPhoto",
|
||||
"children": [
|
||||
{"type":"condition","name":"battery_above","params":{"threshold":0.3}},
|
||||
{"type":"condition","name":"gps_status","params":{"min_satellites":8}},
|
||||
{
|
||||
"type": "Sequence",
|
||||
"name":"EmergencyHandler",
|
||||
"params": {},
|
||||
"name": "PhotoIfFound",
|
||||
"children": [
|
||||
{"type":"action","name":"emergency_return","params":{"reason":"safety_breach"}},
|
||||
{"type":"action","name":"land","params":{"mode":"home"}}
|
||||
{"type":"condition","name":"object_detected","params":{"target_class":"person","description":"扎辫子女子"}},
|
||||
{"type":"action","name":"take_photos","params":{"target_class":"person","description":"扎辫子女子","track_time":10.0}}
|
||||
]
|
||||
},
|
||||
{"type":"action","name":"loiter","params":{"duration":5.0}} // 未发现时的备选动作
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 场景 2:带中断逻辑的巡逻(Selector 示例)
|
||||
**指令**:“飞往航点A。如果途中发现可疑人员,则悬停。”
|
||||
**结构**:
|
||||
```json
|
||||
{
|
||||
"root": {
|
||||
"type": "Sequence",
|
||||
"children": [
|
||||
{"type":"action","name":"takeoff","params":{"altitude":10.0}},
|
||||
{
|
||||
"type": "Selector",
|
||||
"name": "FlyOrDetect",
|
||||
"children": [
|
||||
{
|
||||
"type": "Sequence",
|
||||
"name": "InterruptionLogic",
|
||||
"children": [
|
||||
{"type":"action","name":"object_detect","params":{"target_class":"person"}},
|
||||
{"type":"action","name":"loiter","params":{"duration":5.0}}
|
||||
]
|
||||
},
|
||||
{"type":"action","name":"fly_to_waypoint","params":{"x":100.0,"y":50.0,"z":10.0}}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 场景 3:环绕侦察类任务
|
||||
**指令**:“去面前的大楼三层/12米高,绕着外围看有没有打开的窗户,发现则进行拍照。”
|
||||
**参考知识**:{"text": "面前的大楼外围四个点坐标:A(-24.00, 241.80),B(-108.50, 241.80),C(-108.50, 289.80),D(-24.00, 292.80)。"}
|
||||
**结构**:Sequence (按顺序执行)
|
||||
```json
|
||||
{
|
||||
"root": {
|
||||
"type": "Sequence",
|
||||
"name": "SurroundAndInspect",
|
||||
"children": [
|
||||
{"type":"action","name":"takeoff","params":{"altitude":12.0}},
|
||||
// 移动到A点,并在此过程中持续检测
|
||||
{
|
||||
"type": "Parallel",
|
||||
"name": "FlyAndInspectToA",
|
||||
"children": [
|
||||
{"type":"action","name":"fly_to_waypoint","params":{"x":-24.0,"y":241.8,"z":12.0}},
|
||||
{
|
||||
"type": "Selector",
|
||||
"name": "OpportunisticPhoto",
|
||||
"children": [
|
||||
{
|
||||
"type": "decorator",
|
||||
"name": "SuccessIsFailure",
|
||||
"child": {
|
||||
"type": "Sequence",
|
||||
"name": "DetectAndCapture",
|
||||
"children": [
|
||||
{"type":"action","name":"object_detect","params":{"target_class":"window","description":"open window"}},
|
||||
{"type":"condition","name":"object_detected","params":{"target_class":"window","description":"open window"}},
|
||||
{"type":"action","name":"take_photos","params":{"target_class":"window","description":"open window","track_time":5.0}}
|
||||
]
|
||||
}
|
||||
},
|
||||
{"type":"action","name":"loiter","params":{"duration":0.1}} // 占位动作
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
// 移动到B点...
|
||||
{
|
||||
"type": "Parallel",
|
||||
"name": "FlyAndInspectToB",
|
||||
"children": [
|
||||
{"type":"action","name":"fly_to_waypoint","params":{"x":-108.5,"y":241.8,"z":12.0}},
|
||||
{
|
||||
"type": "Selector",
|
||||
"name": "OpportunisticPhoto",
|
||||
"children": [
|
||||
{
|
||||
"type": "decorator",
|
||||
"name": "SuccessIsFailure",
|
||||
"child": {
|
||||
"type": "Sequence",
|
||||
"name": "DetectAndCapture",
|
||||
"children": [
|
||||
{"type":"action","name":"object_detect","params":{"target_class":"window","description":"open window"}},
|
||||
{"type":"condition","name":"object_detected","params":{"target_class":"window","description":"open window"}},
|
||||
{"type":"action","name":"take_photos","params":{"target_class":"window","description":"open window","track_time":5.0}}
|
||||
]
|
||||
}
|
||||
},
|
||||
{"type":"action","name":"loiter","params":{"duration":0.1}}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
// 移动到C点...
|
||||
{
|
||||
"type": "Parallel",
|
||||
"name": "FlyAndInspectToC",
|
||||
"children": [
|
||||
{"type":"action","name":"fly_to_waypoint","params":{"x":-108.5,"y":289.8,"z":12.0}},
|
||||
{
|
||||
"type": "Selector",
|
||||
"name": "OpportunisticPhoto",
|
||||
"children": [
|
||||
{
|
||||
"type": "decorator",
|
||||
"name": "SuccessIsFailure",
|
||||
"child": {
|
||||
"type": "Sequence",
|
||||
"name": "DetectAndCapture",
|
||||
"children": [
|
||||
{"type":"action","name":"object_detect","params":{"target_class":"window","description":"open window"}},
|
||||
{"type":"condition","name":"object_detected","params":{"target_class":"window","description":"open window"}},
|
||||
{"type":"action","name":"take_photos","params":{"target_class":"window","description":"open window","track_time":5.0}}
|
||||
]
|
||||
}
|
||||
},
|
||||
{"type":"action","name":"loiter","params":{"duration":0.1}}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
// 移动到D点...
|
||||
{
|
||||
"type": "Parallel",
|
||||
"name": "FlyAndInspectToD",
|
||||
"children": [
|
||||
{"type":"action","name":"fly_to_waypoint","params":{"x":-24.0,"y":292.8,"z":12.0}},
|
||||
{
|
||||
"type": "Selector",
|
||||
"name": "OpportunisticPhoto",
|
||||
"children": [
|
||||
{
|
||||
"type": "decorator",
|
||||
"name": "SuccessIsFailure",
|
||||
"child": {
|
||||
"type": "Sequence",
|
||||
"name": "DetectAndCapture",
|
||||
"children": [
|
||||
{"type":"action","name":"object_detect","params":{"target_class":"window","description":"open window"}},
|
||||
{"type":"condition","name":"object_detected","params":{"target_class":"window","description":"open window"}},
|
||||
{"type":"action","name":"take_photos","params":{"target_class":"window","description":"open window","track_time":5.0}}
|
||||
]
|
||||
}
|
||||
},
|
||||
{"type":"action","name":"loiter","params":{"duration":0.1}}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -96,22 +266,21 @@
|
||||
}
|
||||
```
|
||||
|
||||
## 五、优先级排序任务通用示例
|
||||
当用户指令中明确提出有多个待考察且具有优先级关系的物体时,节点描述须为优先级关系。
|
||||
| 用户指令场景 | `target_class` | `description` |
|
||||
|-----------------------------|-----------------|-------------------------|
|
||||
| 红气球>蓝气球>绿气球 | `balloon` | `(红>蓝>绿)` |
|
||||
| 军用卡车>民用卡车>面包车 | `truck` | `(军用卡车>民用卡车>面包车)` |
|
||||
|
||||
## 四、优先级排序任务通用示例
|
||||
当用户指令中明确提出有多个待考察且具有优先级关系的物体时,节点描述须为优先级关系。比如当指令为已知有三个气球,危险级关系为红色气球大于蓝色气球大于绿色气球,要求优先跟踪最危险的气球时,节点的描述参考下表情形。
|
||||
| 用户指令场景 | `target_class` | `description` | 核心节点示例(search_pattern) |
|
||||
|-----------------------------|-----------------|-------------------------|------------------------------------------------------------------------------------------------|
|
||||
| 红气球>蓝气球>绿气球 | `balloon` | `(红>蓝>绿)` | `{"type":"action","name":"search_pattern","params":{"pattern_type":"grid","center_x":200,"center_y":150,"center_z":10,"radius":50,"target_class":"balloon","description":"(红>蓝>绿)"}}` |
|
||||
| 军用卡车>民用卡车>面包车 | `truck` | `(军用卡车>民用卡车>面包车)` | `{"type":"action","name":"object_detect","params":{"target_class":"truck","description":"(军用卡车>民用卡车>面包车)"}}` |
|
||||
## 六、高频错误规避
|
||||
1. 优先级排序不可修改`target_class`,仅用`description`填排序规则;
|
||||
2. `track_object`必传`track_time`;
|
||||
3. `gps_status`的`min_satellites`必须在6-15之间;
|
||||
4. 严禁输出 markdown 代码块标记,直接输出 JSON 纯文本;
|
||||
5. 控制流节点的 `type` 必须是 `"Sequence"`, `"Selector"` 或 `"Parallel"`;
|
||||
6. rotate与rotate_search动作节点意思是无人机以自身为原点旋转,而非围绕外部点旋转
|
||||
7. 当用户指令中要求执行动作前增加人工确认时,比如“我确认后拍照”,则必须在拍照动作前增加manual_confirmation节点
|
||||
|
||||
|
||||
## 五、高频错误规避(确保验证通过)
|
||||
1. 优先级排序不可修改`target_class`:如“民用卡车、面包车与军用卡车中,军用卡车优先”,`target_class`仍为`truck`,仅用`description`填排序规则;
|
||||
2. 在没有明确指出物体之间的优先级关系情况下,`description`字段只描述物体属性本身,严禁与用户指令中不存在的物体进行排序;
|
||||
3. `track_object`必传`track_time`:不可用`duration`替代(如跟踪30秒填`"track_time":30.0`);
|
||||
4. `gps_status`的`min_satellites`必须在6-15之间(如8,不可缺省);
|
||||
5. 无自定义节点:“锁定高优先级目标”需通过`object_detect`+`object_detected`实现,不可用“lock_high_risk_target”。
|
||||
|
||||
|
||||
## 六、输出要求
|
||||
仅输出1个严格符合上述所有规则的JSON对象,**确保:1. 优先级排序逻辑正确填入`description`;2. `target_class`匹配预定义列表;3. 行为树结构不变;4. 后端解析与Schema验证无错误**,无任何冗余内容。
|
||||
## 七、输出要求
|
||||
仅输出1个严格符合上述所有规则的JSON对象。
|
||||
|
||||
@@ -52,7 +52,7 @@ def _parse_allowed_nodes_from_prompt(prompt_text: str) -> tuple[Set[str], Set[st
|
||||
"""
|
||||
try:
|
||||
# 使用更精确的正则表达式匹配节点定义部分
|
||||
node_section_pattern = r"#### 2\. 可用节点定义.*?```json\s*({.*?})\s*```"
|
||||
node_section_pattern = r"#### 1\. 可用节点定义.*?```json\s*({.*?})\s*```"
|
||||
match = re.search(node_section_pattern, prompt_text, re.DOTALL | re.IGNORECASE)
|
||||
|
||||
if not match:
|
||||
@@ -144,51 +144,12 @@ def _fallback_parse_nodes(prompt_text: str) -> tuple[Set[str], Set[str]]:
|
||||
logging.error("在所有JSON代码块中都没有找到有效的节点定义结构。")
|
||||
return set(), set()
|
||||
|
||||
def _find_nodes_by_name(node: Dict, target_name: str) -> List[Dict]:
|
||||
"""递归查找所有指定名称的节点"""
|
||||
nodes_found = []
|
||||
|
||||
if node.get("name") == target_name:
|
||||
nodes_found.append(node)
|
||||
|
||||
# 递归搜索子节点
|
||||
for child in node.get("children", []):
|
||||
nodes_found.extend(_find_nodes_by_name(child, target_name))
|
||||
|
||||
return nodes_found
|
||||
|
||||
def _validate_safety_monitoring(pytree_instance: dict) -> bool:
|
||||
"""验证行为树是否包含必要的安全监控"""
|
||||
root_node = pytree_instance.get("root", {})
|
||||
|
||||
# 查找所有电池监控节点
|
||||
battery_nodes = _find_nodes_by_name(root_node, "battery_above")
|
||||
|
||||
# 检查是否包含安全监控结构
|
||||
safety_monitors = _find_nodes_by_name(root_node, "SafetyMonitor")
|
||||
|
||||
if not battery_nodes and not safety_monitors:
|
||||
logging.warning("⚠️ 安全警告: 行为树中没有发现电池监控节点或安全监控器")
|
||||
return False
|
||||
|
||||
# 检查电池阈值设置是否合理
|
||||
for battery_node in battery_nodes:
|
||||
threshold = battery_node.get("params", {}).get("threshold")
|
||||
if threshold is not None:
|
||||
if threshold < 0.25:
|
||||
logging.warning(f"⚠️ 安全警告: 电池阈值设置过低 ({threshold}),建议不低于0.25")
|
||||
elif threshold > 0.5:
|
||||
logging.warning(f"⚠️ 安全警告: 电池阈值设置过高 ({threshold}),可能影响任务执行")
|
||||
|
||||
logging.info("✅ 安全监控验证通过")
|
||||
return True
|
||||
|
||||
def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> dict:
|
||||
"""
|
||||
根据允许的行动和条件节点,动态生成一个JSON Schema。
|
||||
"""
|
||||
# 所有可能的节点类型
|
||||
node_types = ["action", "condition", "Sequence", "Selector", "Parallel"]
|
||||
node_types = ["action", "condition", "Sequence", "Selector", "Parallel", "decorator"]
|
||||
|
||||
# 目标检测相关的类别枚举
|
||||
target_classes = [
|
||||
@@ -201,38 +162,44 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
|
||||
"sandwich", "orange", "broccoli", "carrot", "hot_dog", "pizza", "donut", "cake", "chair",
|
||||
"couch", "potted_plant", "bed", "dining_table", "toilet", "tv", "laptop", "mouse", "remote",
|
||||
"keyboard", "cell_phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book",
|
||||
"clock", "vase", "scissors", "teddy_bear", "hair_drier", "toothbrush","balloon"
|
||||
"clock", "vase", "scissors", "teddy_bear", "hair_drier", "toothbrush","balloon","trash","window","garbage"
|
||||
]
|
||||
|
||||
# 递归节点定义
|
||||
node_definition = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {"type": "string", "enum": node_types},
|
||||
# 修改:手动构造不区分大小写的正则,避免使用不支持的 (?i) 标志
|
||||
# 匹配: action, condition, sequence, selector, parallel, decorator (忽略大小写)
|
||||
"type": {
|
||||
"type": "string",
|
||||
"pattern": "^([Aa][Cc][Tt][Ii][Oo][Nn]|[Cc][Oo][Nn][Dd][Ii][Tt][Ii][Oo][Nn]|[Ss][Ee][Qq][Uu][Ee][Nn][Cc][Ee]|[Ss][Ee][Ll][Ee][Cc][Tt][Oo][Rr]|[Pp][Aa][Rr][Aa][Ll][Ll][Ee][Ll]|[Dd][Ee][Cc][Oo][Rr][Aa][Tt][Oo][Rr])$"
|
||||
},
|
||||
"name": {"type": "string"},
|
||||
"params": {"type": "object"},
|
||||
"children": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/definitions/node"}
|
||||
}
|
||||
},
|
||||
"child": {"$ref": "#/definitions/node"}
|
||||
},
|
||||
"required": ["type", "name"],
|
||||
"allOf": [
|
||||
# 动作节点验证
|
||||
# 动作节点验证 (忽略大小写)
|
||||
{
|
||||
"if": {"properties": {"type": {"const": "action"}}},
|
||||
"if": {"properties": {"type": {"pattern": "^[Aa][Cc][Tt][Ii][Oo][Nn]$"}}},
|
||||
"then": {"properties": {"name": {"enum": sorted(list(allowed_actions))}}}
|
||||
},
|
||||
# 条件节点验证
|
||||
# 条件节点验证 (忽略大小写)
|
||||
{
|
||||
"if": {"properties": {"type": {"const": "condition"}}},
|
||||
"if": {"properties": {"type": {"pattern": "^[Cc][Oo][Nn][Dd][Ii][Tt][Ii][Oo][Nn]$"}}},
|
||||
"then": {"properties": {"name": {"enum": sorted(list(allowed_conditions))}}}
|
||||
},
|
||||
# 目标检测动作节点的参数验证
|
||||
# 目标检测动作节点的参数验证 (忽略大小写)
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {"const": "action"},
|
||||
"type": {"pattern": "^[Aa][Cc][Tt][Ii][Oo][Nn]$"},
|
||||
"name": {"const": "object_detect"}
|
||||
}
|
||||
},
|
||||
@@ -251,11 +218,11 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
|
||||
}
|
||||
}
|
||||
},
|
||||
# 目标检测条件节点的参数验证
|
||||
# 目标检测条件节点的参数验证 (忽略大小写)
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {"const": "condition"},
|
||||
"type": {"pattern": "^[Cc][Oo][Nn][Dd][Ii][Tt][Ii][Oo][Nn]$"},
|
||||
"name": {"const": "object_detected"}
|
||||
}
|
||||
},
|
||||
@@ -274,11 +241,11 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
|
||||
}
|
||||
}
|
||||
},
|
||||
# 电池监控节点的参数验证
|
||||
# 电池监控节点的参数验证 (忽略大小写)
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {"const": "condition"},
|
||||
"type": {"pattern": "^[Cc][Oo][Nn][Dd][Ii][Tt][Ii][Oo][Nn]$"},
|
||||
"name": {"const": "battery_above"}
|
||||
}
|
||||
},
|
||||
@@ -295,11 +262,11 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
|
||||
}
|
||||
}
|
||||
},
|
||||
# GPS状态节点的参数验证
|
||||
# GPS状态节点的参数验证 (忽略大小写)
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {"const": "condition"},
|
||||
"type": {"pattern": "^[Cc][Oo][Nn][Dd][Ii][Tt][Ii][Oo][Nn]$"},
|
||||
"name": {"const": "gps_status"}
|
||||
}
|
||||
},
|
||||
@@ -337,7 +304,9 @@ def _generate_pytree_schema(allowed_actions: set, allowed_conditions: set) -> di
|
||||
|
||||
def _generate_simple_mode_schema(allowed_actions: set) -> dict:
|
||||
"""
|
||||
生成简单模式JSON Schema:{"mode":"simple","action":{...}}
|
||||
生成简单模式JSON Schema:{"root":{"type":"action","name":"...","params":{...}}}
|
||||
简单模式与复杂模式使用相同的格式(root字段),但要求root必须是action类型且没有children。
|
||||
严格按照提示词要求:root节点必须是action类型节点,不能是控制流节点(即不能有children)。
|
||||
仅校验动作名称在允许集合内,以及基本结构完整性;参数按对象形状放宽,由上游提示词与运行时再约束。
|
||||
"""
|
||||
schema = {
|
||||
@@ -345,19 +314,20 @@ def _generate_simple_mode_schema(allowed_actions: set) -> dict:
|
||||
"title": "SimpleMode",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mode": {"type": "string", "const": "simple"},
|
||||
"action": {
|
||||
"root": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string", "enum": sorted(list(allowed_actions))},
|
||||
"params": {"type": "object"}
|
||||
"type": {"type": "string", "const": "action"}, # 必须是action类型,不能是控制流节点(Sequence/Selector/Parallel)
|
||||
"name": {"type": "string", "enum": sorted(list(allowed_actions))}, # 动作名称必须在允许列表中
|
||||
"params": {"type": "object"} # params是对象,具体参数由提示词和运行时约束
|
||||
},
|
||||
"required": ["name"],
|
||||
"additionalProperties": True
|
||||
"required": ["type", "name"], # type和name是必需的,params可选
|
||||
"additionalProperties": True # 允许root节点有其他属性(如额外的元数据)
|
||||
# 注意:children字段的检查在验证后手动进行,因为JSON Schema的not/allOf在检查不存在字段时可能有问题
|
||||
}
|
||||
},
|
||||
"required": ["mode", "action"],
|
||||
"additionalProperties": False
|
||||
"required": ["root"], # 顶层必须有root字段
|
||||
"additionalProperties": False # 顶层只能有root字段,不能有其他字段(如mode等)
|
||||
}
|
||||
return schema
|
||||
|
||||
@@ -369,10 +339,7 @@ def _validate_pytree_with_schema(pytree_instance: dict, schema: dict) -> bool:
|
||||
jsonschema.validate(instance=pytree_instance, schema=schema)
|
||||
logging.info("✅ JSON Schema验证成功")
|
||||
|
||||
# 额外验证安全监控
|
||||
safety_valid = _validate_safety_monitoring(pytree_instance)
|
||||
|
||||
return True and safety_valid
|
||||
return True
|
||||
except jsonschema.ValidationError as e:
|
||||
logging.warning("❌ Pytree验证失败")
|
||||
logging.warning(f"错误信息: {e.message}")
|
||||
@@ -500,6 +467,10 @@ def _add_nodes_and_edges(node: dict, dot, parent_id: str | None = None) -> str:
|
||||
shape = 'ellipse'
|
||||
style = 'filled'
|
||||
fillcolor = '#e1d5e7' # 紫色
|
||||
elif node_type == 'decorator':
|
||||
shape = 'doubleoctagon'
|
||||
style = 'filled'
|
||||
fillcolor = '#f8cecc' # 浅红
|
||||
|
||||
# 特别标记安全相关节点
|
||||
if node.get('name') in ['battery_above', 'gps_status', 'SafetyMonitor']:
|
||||
@@ -512,11 +483,14 @@ def _add_nodes_and_edges(node: dict, dot, parent_id: str | None = None) -> str:
|
||||
if parent_id:
|
||||
dot.edge(parent_id, current_id)
|
||||
|
||||
# 递归处理子节点
|
||||
# 递归处理子节点 (Sequence, Selector, Parallel 等)
|
||||
children = node.get("children", [])
|
||||
if not children:
|
||||
return current_id
|
||||
|
||||
# 兼容 decorator 类型的 child 字段 (处理为单元素列表以便统一逻辑)
|
||||
if node_type == 'decorator' and 'child' in node:
|
||||
children = [node['child']]
|
||||
|
||||
if children:
|
||||
# 记录所有子节点的ID
|
||||
child_ids = []
|
||||
|
||||
@@ -532,8 +506,10 @@ def _add_nodes_and_edges(node: dict, dot, parent_id: str | None = None) -> str:
|
||||
for cid in child_ids:
|
||||
s.node(cid)
|
||||
|
||||
# 行为树中,所有类型的节点都只是父连子,不需要子节点间的额外连接
|
||||
# Sequence、Selector、Parallel 的执行逻辑由行为树引擎处理,不需要在可视化中体现
|
||||
# 递归处理单子节点 (Decorator) - 已合并到 children 处理逻辑中,此处删除旧逻辑
|
||||
# child = node.get("child")
|
||||
# if child:
|
||||
# _add_nodes_and_edges(child, dot, current_id)
|
||||
|
||||
return current_id
|
||||
|
||||
@@ -585,7 +561,7 @@ class PyTreeGenerator:
|
||||
self.complex_llm_client = openai.OpenAI(api_key=self.api_key, base_url=self.complex_base_url)
|
||||
|
||||
# --- ChromaDB Client Setup ---
|
||||
vector_store_path = os.path.abspath(os.path.join(self.base_dir, '..', '..', 'tools', 'vector_store'))
|
||||
vector_store_path = os.path.abspath(os.path.join(self.base_dir, '..', '..', 'tools', 'rag','vector_store'))
|
||||
self.chroma_client = chromadb.PersistentClient(path=vector_store_path)
|
||||
|
||||
# Explicitly use the remote embedding function for queries
|
||||
@@ -722,7 +698,7 @@ class PyTreeGenerator:
|
||||
pytree_str = combined_text if combined_text else (msg_content or "")
|
||||
raw_full_text_for_logging = pytree_str # 保存完整原文(含 <think>)以便失败时完整打印
|
||||
|
||||
# 提取 <think> 推理链内容(若存在)
|
||||
# 提取 <think> 推理链内容(若有)
|
||||
reasoning_text = None
|
||||
try:
|
||||
think_match = re.search(r"<think>([\s\S]*?)</think>", pytree_str)
|
||||
@@ -777,6 +753,13 @@ class PyTreeGenerator:
|
||||
if mode == "simple":
|
||||
try:
|
||||
jsonschema.validate(instance=pytree_dict, schema=self.simple_schema)
|
||||
# 手动检查:简单模式的root节点不能有children(或children必须是空数组)
|
||||
root_node = pytree_dict.get('root', {})
|
||||
if 'children' in root_node:
|
||||
children = root_node.get('children', [])
|
||||
if isinstance(children, list) and len(children) > 0:
|
||||
logging.warning(f"❌ 简单模式验证失败: root节点不能有children,但发现 {len(children)} 个子节点")
|
||||
continue
|
||||
logging.info("✅ 简单模式JSON Schema验证成功")
|
||||
except jsonschema.ValidationError as e:
|
||||
logging.warning(f"❌ 简单模式验证失败: {e.message}")
|
||||
@@ -784,16 +767,13 @@ class PyTreeGenerator:
|
||||
# 附加元信息并生成简单可视化(单动作)
|
||||
plan_id = str(uuid.uuid4())
|
||||
pytree_dict['plan_id'] = plan_id
|
||||
# 简单模式可视化:构造一个简化节点图
|
||||
# 简单模式可视化:使用root节点(已经是action类型)
|
||||
try:
|
||||
vis_filename = "py_tree.png"
|
||||
vis_path = os.path.join(self.vis_dir, vis_filename)
|
||||
simple_node = {
|
||||
"type": "action",
|
||||
"name": pytree_dict.get('action', {}).get('name', 'action'),
|
||||
"params": pytree_dict.get('action', {}).get('params', {})
|
||||
}
|
||||
_visualize_pytree(simple_node, os.path.splitext(vis_path)[0])
|
||||
# 简单模式的root节点就是action节点,直接使用
|
||||
root_node = pytree_dict.get('root', {})
|
||||
_visualize_pytree(root_node, os.path.splitext(vis_path)[0])
|
||||
pytree_dict['visualization_url'] = f"/static/{vis_filename}"
|
||||
except Exception as e:
|
||||
logging.warning(f"简单模式可视化失败: {e}")
|
||||
@@ -820,46 +800,7 @@ class PyTreeGenerator:
|
||||
pytree_dict['final_prompt'] = final_prompt
|
||||
return pytree_dict
|
||||
|
||||
# 复杂模式回退:若模型误返回简单结构,则自动包装为含安全监控的行为树
|
||||
if mode == "complex" and isinstance(pytree_dict, dict) and 'root' not in pytree_dict:
|
||||
try:
|
||||
jsonschema.validate(instance=pytree_dict, schema=self.simple_schema)
|
||||
logging.warning("⚠️ 复杂模式生成了简单结构,触发自动包装为完整行为树的回退逻辑。")
|
||||
simple_action_obj = pytree_dict.get('action') or {}
|
||||
action_name = simple_action_obj.get('name')
|
||||
action_params = simple_action_obj.get('params') if isinstance(simple_action_obj.get('params'), dict) else {}
|
||||
|
||||
safety_selector = {
|
||||
"type": "Selector",
|
||||
"name": "SafetyMonitor",
|
||||
"params": {"memory": True},
|
||||
"children": [
|
||||
{"type": "condition", "name": "battery_above", "params": {"threshold": 0.3}},
|
||||
{"type": "condition", "name": "gps_status", "params": {"min_satellites": 8}},
|
||||
{"type": "Sequence", "name": "EmergencyHandler", "children": [
|
||||
{"type": "action", "name": "emergency_return", "params": {"reason": "safety_breach"}},
|
||||
{"type": "action", "name": "land", "params": {"mode": "home"}}
|
||||
]}
|
||||
]
|
||||
}
|
||||
|
||||
main_children = [{"type": "action", "name": action_name, "params": action_params}]
|
||||
if action_name != "land":
|
||||
main_children.append({"type": "action", "name": "land", "params": {"mode": "home"}})
|
||||
|
||||
root_parallel = {
|
||||
"type": "Parallel",
|
||||
"name": "MissionWithSafety",
|
||||
"params": {"policy": "all_success"},
|
||||
"children": [
|
||||
{"type": "Sequence", "name": "MainTask", "children": main_children},
|
||||
safety_selector
|
||||
]
|
||||
}
|
||||
pytree_dict = {"root": root_parallel}
|
||||
except jsonschema.ValidationError:
|
||||
# 不符合简单结构,按正常复杂验证继续
|
||||
pass
|
||||
# 验证生成的复杂行为树
|
||||
if _validate_pytree_with_schema(pytree_dict, self.schema):
|
||||
logging.info("✅ 成功生成并验证了Pytree")
|
||||
plan_id = str(uuid.uuid4())
|
||||
|
||||
248
backend_service/venv/bin/Activate.ps1
Normal file
248
backend_service/venv/bin/Activate.ps1
Normal file
@@ -0,0 +1,248 @@
|
||||
<#
|
||||
.Synopsis
|
||||
Activate a Python virtual environment for the current PowerShell session.
|
||||
|
||||
.Description
|
||||
Pushes the python executable for a virtual environment to the front of the
|
||||
$Env:PATH environment variable and sets the prompt to signify that you are
|
||||
in a Python virtual environment. Makes use of the command line switches as
|
||||
well as the `pyvenv.cfg` file values present in the virtual environment.
|
||||
|
||||
.Parameter VenvDir
|
||||
Path to the directory that contains the virtual environment to activate. The
|
||||
default value for this is the parent of the directory that the Activate.ps1
|
||||
script is located within.
|
||||
|
||||
.Parameter Prompt
|
||||
The prompt prefix to display when this virtual environment is activated. By
|
||||
default, this prompt is the name of the virtual environment folder (VenvDir)
|
||||
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
|
||||
|
||||
.Example
|
||||
Activate.ps1
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -Verbose
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and shows extra information about the activation as it executes.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
|
||||
Activates the Python virtual environment located in the specified location.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -Prompt "MyPython"
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and prefixes the current prompt with the specified string (surrounded in
|
||||
parentheses) while the virtual environment is active.
|
||||
|
||||
.Notes
|
||||
On Windows, it may be required to enable this Activate.ps1 script by setting the
|
||||
execution policy for the user. You can do this by issuing the following PowerShell
|
||||
command:
|
||||
|
||||
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
|
||||
For more information on Execution Policies:
|
||||
https://go.microsoft.com/fwlink/?LinkID=135170
|
||||
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$VenvDir,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$Prompt
|
||||
)
|
||||
|
||||
<# Function declarations --------------------------------------------------- #>
|
||||
|
||||
<#
|
||||
.Synopsis
|
||||
Remove all shell session elements added by the Activate script, including the
|
||||
addition of the virtual environment's Python executable from the beginning of
|
||||
the PATH variable.
|
||||
|
||||
.Parameter NonDestructive
|
||||
If present, do not remove this function from the global namespace for the
|
||||
session.
|
||||
|
||||
#>
|
||||
function global:deactivate ([switch]$NonDestructive) {
|
||||
# Revert to original values
|
||||
|
||||
# The prior prompt:
|
||||
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
|
||||
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
|
||||
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
|
||||
}
|
||||
|
||||
# The prior PYTHONHOME:
|
||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
|
||||
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
|
||||
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
|
||||
}
|
||||
|
||||
# The prior PATH:
|
||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
|
||||
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
|
||||
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
|
||||
}
|
||||
|
||||
# Just remove the VIRTUAL_ENV altogether:
|
||||
if (Test-Path -Path Env:VIRTUAL_ENV) {
|
||||
Remove-Item -Path env:VIRTUAL_ENV
|
||||
}
|
||||
|
||||
# Just remove VIRTUAL_ENV_PROMPT altogether.
|
||||
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
|
||||
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
|
||||
}
|
||||
|
||||
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
|
||||
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
|
||||
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
|
||||
}
|
||||
|
||||
# Leave deactivate function in the global namespace if requested:
|
||||
if (-not $NonDestructive) {
|
||||
Remove-Item -Path function:deactivate
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.Description
|
||||
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
|
||||
given folder, and returns them in a map.
|
||||
|
||||
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
|
||||
two strings separated by `=` (with any amount of whitespace surrounding the =)
|
||||
then it is considered a `key = value` line. The left hand string is the key,
|
||||
the right hand is the value.
|
||||
|
||||
If the value starts with a `'` or a `"` then the first and last character is
|
||||
stripped from the value before being captured.
|
||||
|
||||
.Parameter ConfigDir
|
||||
Path to the directory that contains the `pyvenv.cfg` file.
|
||||
#>
|
||||
function Get-PyVenvConfig(
|
||||
[String]
|
||||
$ConfigDir
|
||||
) {
|
||||
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
|
||||
|
||||
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
|
||||
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
|
||||
|
||||
# An empty map will be returned if no config file is found.
|
||||
$pyvenvConfig = @{ }
|
||||
|
||||
if ($pyvenvConfigPath) {
|
||||
|
||||
Write-Verbose "File exists, parse `key = value` lines"
|
||||
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
|
||||
|
||||
$pyvenvConfigContent | ForEach-Object {
|
||||
$keyval = $PSItem -split "\s*=\s*", 2
|
||||
if ($keyval[0] -and $keyval[1]) {
|
||||
$val = $keyval[1]
|
||||
|
||||
# Remove extraneous quotations around a string value.
|
||||
if ("'""".Contains($val.Substring(0, 1))) {
|
||||
$val = $val.Substring(1, $val.Length - 2)
|
||||
}
|
||||
|
||||
$pyvenvConfig[$keyval[0]] = $val
|
||||
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
|
||||
}
|
||||
}
|
||||
}
|
||||
return $pyvenvConfig
|
||||
}
|
||||
|
||||
|
||||
<# Begin Activate script --------------------------------------------------- #>
|
||||
|
||||
# Determine the containing directory of this script
|
||||
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$VenvExecDir = Get-Item -Path $VenvExecPath
|
||||
|
||||
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
|
||||
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
|
||||
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
||||
|
||||
# Set values required in priority: CmdLine, ConfigFile, Default
|
||||
# First, get the location of the virtual environment, it might not be
|
||||
# VenvExecDir if specified on the command line.
|
||||
if ($VenvDir) {
|
||||
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
||||
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
||||
Write-Verbose "VenvDir=$VenvDir"
|
||||
}
|
||||
|
||||
# Next, read the `pyvenv.cfg` file to determine any required value such
|
||||
# as `prompt`.
|
||||
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
||||
|
||||
# Next, set the prompt from the command line, or the config file, or
|
||||
# just use the name of the virtual environment folder.
|
||||
if ($Prompt) {
|
||||
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
||||
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
||||
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
||||
$Prompt = $pyvenvCfg['prompt'];
|
||||
}
|
||||
else {
|
||||
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
|
||||
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
|
||||
$Prompt = Split-Path -Path $venvDir -Leaf
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose "Prompt = '$Prompt'"
|
||||
Write-Verbose "VenvDir='$VenvDir'"
|
||||
|
||||
# Deactivate any currently active virtual environment, but leave the
|
||||
# deactivate function in place.
|
||||
deactivate -nondestructive
|
||||
|
||||
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
|
||||
# that there is an activated venv.
|
||||
$env:VIRTUAL_ENV = $VenvDir
|
||||
|
||||
$env:VIRTUAL_ENV_PROMPT = $Prompt
|
||||
|
||||
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
||||
|
||||
Write-Verbose "Setting prompt to '$Prompt'"
|
||||
|
||||
# Set the prompt to include the env name
|
||||
# Make sure _OLD_VIRTUAL_PROMPT is global
|
||||
function global:_OLD_VIRTUAL_PROMPT { "" }
|
||||
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
|
||||
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
|
||||
|
||||
function global:prompt {
|
||||
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
|
||||
_OLD_VIRTUAL_PROMPT
|
||||
}
|
||||
}
|
||||
|
||||
# Clear PYTHONHOME
|
||||
if (Test-Path -Path Env:PYTHONHOME) {
|
||||
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
|
||||
Remove-Item -Path Env:PYTHONHOME
|
||||
}
|
||||
|
||||
# Add the venv to the PATH
|
||||
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
|
||||
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
|
||||
76
backend_service/venv/bin/activate
Normal file
76
backend_service/venv/bin/activate
Normal file
@@ -0,0 +1,76 @@
|
||||
# This file must be used with "source bin/activate" *from bash*
|
||||
# You cannot run it directly
|
||||
|
||||
deactivate () {
|
||||
# reset old environment variables
|
||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||
export PATH
|
||||
unset _OLD_VIRTUAL_PATH
|
||||
fi
|
||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||
export PYTHONHOME
|
||||
unset _OLD_VIRTUAL_PYTHONHOME
|
||||
fi
|
||||
|
||||
# Call hash to forget past locations. Without forgetting
|
||||
# past locations the $PATH changes we made may not be respected.
|
||||
# See "man bash" for more details. hash is usually a builtin of your shell
|
||||
hash -r 2> /dev/null
|
||||
|
||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||
export PS1
|
||||
unset _OLD_VIRTUAL_PS1
|
||||
fi
|
||||
|
||||
unset VIRTUAL_ENV
|
||||
unset VIRTUAL_ENV_PROMPT
|
||||
if [ ! "${1:-}" = "nondestructive" ] ; then
|
||||
# Self destruct!
|
||||
unset -f deactivate
|
||||
fi
|
||||
}
|
||||
|
||||
# unset irrelevant variables
|
||||
deactivate nondestructive
|
||||
|
||||
# on Windows, a path can contain colons and backslashes and has to be converted:
|
||||
case "$(uname)" in
|
||||
CYGWIN*|MSYS*|MINGW*)
|
||||
# transform D:\path\to\venv to /d/path/to/venv on MSYS and MINGW
|
||||
# and to /cygdrive/d/path/to/venv on Cygwin
|
||||
VIRTUAL_ENV=$(cygpath /home/a/DronePlanning/backend_service/venv)
|
||||
export VIRTUAL_ENV
|
||||
;;
|
||||
*)
|
||||
# use the path as-is
|
||||
export VIRTUAL_ENV=/home/a/DronePlanning/backend_service/venv
|
||||
;;
|
||||
esac
|
||||
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
PATH="$VIRTUAL_ENV/"bin":$PATH"
|
||||
export PATH
|
||||
|
||||
VIRTUAL_ENV_PROMPT=venv
|
||||
export VIRTUAL_ENV_PROMPT
|
||||
|
||||
# unset PYTHONHOME if set
|
||||
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||
unset PYTHONHOME
|
||||
fi
|
||||
|
||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||
PS1="("venv") ${PS1:-}"
|
||||
export PS1
|
||||
fi
|
||||
|
||||
# Call hash to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
hash -r 2> /dev/null
|
||||
27
backend_service/venv/bin/activate.csh
Normal file
27
backend_service/venv/bin/activate.csh
Normal file
@@ -0,0 +1,27 @@
|
||||
# This file must be used with "source bin/activate.csh" *from csh*.
|
||||
# You cannot run it directly.
|
||||
|
||||
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
||||
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
||||
|
||||
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
setenv VIRTUAL_ENV /home/a/DronePlanning/backend_service/venv
|
||||
|
||||
set _OLD_VIRTUAL_PATH="$PATH"
|
||||
setenv PATH "$VIRTUAL_ENV/"bin":$PATH"
|
||||
setenv VIRTUAL_ENV_PROMPT venv
|
||||
|
||||
|
||||
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||
|
||||
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||
set prompt = "("venv") $prompt:q"
|
||||
endif
|
||||
|
||||
alias pydoc python -m pydoc
|
||||
|
||||
rehash
|
||||
69
backend_service/venv/bin/activate.fish
Normal file
69
backend_service/venv/bin/activate.fish
Normal file
@@ -0,0 +1,69 @@
|
||||
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
|
||||
# (https://fishshell.com/). You cannot run it directly.
|
||||
|
||||
function deactivate -d "Exit virtual environment and return to normal shell environment"
|
||||
# reset old environment variables
|
||||
if test -n "$_OLD_VIRTUAL_PATH"
|
||||
set -gx PATH $_OLD_VIRTUAL_PATH
|
||||
set -e _OLD_VIRTUAL_PATH
|
||||
end
|
||||
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
||||
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
||||
set -e _OLD_VIRTUAL_PYTHONHOME
|
||||
end
|
||||
|
||||
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
||||
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||
# prevents error when using nested fish instances (Issue #93858)
|
||||
if functions -q _old_fish_prompt
|
||||
functions -e fish_prompt
|
||||
functions -c _old_fish_prompt fish_prompt
|
||||
functions -e _old_fish_prompt
|
||||
end
|
||||
end
|
||||
|
||||
set -e VIRTUAL_ENV
|
||||
set -e VIRTUAL_ENV_PROMPT
|
||||
if test "$argv[1]" != "nondestructive"
|
||||
# Self-destruct!
|
||||
functions -e deactivate
|
||||
end
|
||||
end
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
set -gx VIRTUAL_ENV /home/a/DronePlanning/backend_service/venv
|
||||
|
||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||
set -gx PATH "$VIRTUAL_ENV/"bin $PATH
|
||||
set -gx VIRTUAL_ENV_PROMPT venv
|
||||
|
||||
# Unset PYTHONHOME if set.
|
||||
if set -q PYTHONHOME
|
||||
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||
set -e PYTHONHOME
|
||||
end
|
||||
|
||||
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||
# fish uses a function instead of an env var to generate the prompt.
|
||||
|
||||
# Save the current fish_prompt function as the function _old_fish_prompt.
|
||||
functions -c fish_prompt _old_fish_prompt
|
||||
|
||||
# With the original prompt function renamed, we can override with our own.
|
||||
function fish_prompt
|
||||
# Save the return status of the last command.
|
||||
set -l old_status $status
|
||||
|
||||
# Output the venv prompt; color taken from the blue of the Python logo.
|
||||
printf "%s(%s)%s " (set_color 4B8BBE) venv (set_color normal)
|
||||
|
||||
# Restore the return status of the previous command.
|
||||
echo "exit $old_status" | .
|
||||
# Output the original/"old" prompt.
|
||||
_old_fish_prompt
|
||||
end
|
||||
|
||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||
end
|
||||
7
backend_service/venv/bin/chroma
Executable file
7
backend_service/venv/bin/chroma
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from chromadb.cli.cli import app
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(app())
|
||||
7
backend_service/venv/bin/coloredlogs
Executable file
7
backend_service/venv/bin/coloredlogs
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from coloredlogs.cli import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/distro
Executable file
7
backend_service/venv/bin/distro
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from distro.distro import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/dotenv
Executable file
7
backend_service/venv/bin/dotenv
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from dotenv.__main__ import cli
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(cli())
|
||||
7
backend_service/venv/bin/f2py
Executable file
7
backend_service/venv/bin/f2py
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from numpy.f2py.f2py2e import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/fastapi
Executable file
7
backend_service/venv/bin/fastapi
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from fastapi.cli import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/hf
Executable file
7
backend_service/venv/bin/hf
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from huggingface_hub.cli.hf import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/httpx
Executable file
7
backend_service/venv/bin/httpx
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from httpx import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/humanfriendly
Executable file
7
backend_service/venv/bin/humanfriendly
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from humanfriendly.cli import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/isympy
Executable file
7
backend_service/venv/bin/isympy
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from isympy import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/jsonschema
Executable file
7
backend_service/venv/bin/jsonschema
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from jsonschema.cli import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/markdown-it
Executable file
7
backend_service/venv/bin/markdown-it
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from markdown_it.cli.parse import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/normalizer
Executable file
7
backend_service/venv/bin/normalizer
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from charset_normalizer.cli import cli_detect
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(cli_detect())
|
||||
7
backend_service/venv/bin/numpy-config
Executable file
7
backend_service/venv/bin/numpy-config
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from numpy._configtool import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/onnxruntime_test
Executable file
7
backend_service/venv/bin/onnxruntime_test
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from onnxruntime.tools.onnxruntime_test import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/openai
Executable file
7
backend_service/venv/bin/openai
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from openai.cli import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/pip
Executable file
7
backend_service/venv/bin/pip
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/pip3
Executable file
7
backend_service/venv/bin/pip3
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/pip3.13
Executable file
7
backend_service/venv/bin/pip3.13
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/pybase64
Executable file
7
backend_service/venv/bin/pybase64
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from pybase64.__main__ import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/pygmentize
Executable file
7
backend_service/venv/bin/pygmentize
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from pygments.cmdline import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/pyproject-build
Executable file
7
backend_service/venv/bin/pyproject-build
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from build.__main__ import entrypoint
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(entrypoint())
|
||||
7
backend_service/venv/bin/pyrsa-decrypt
Executable file
7
backend_service/venv/bin/pyrsa-decrypt
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from rsa.cli import decrypt
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(decrypt())
|
||||
7
backend_service/venv/bin/pyrsa-encrypt
Executable file
7
backend_service/venv/bin/pyrsa-encrypt
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from rsa.cli import encrypt
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(encrypt())
|
||||
7
backend_service/venv/bin/pyrsa-keygen
Executable file
7
backend_service/venv/bin/pyrsa-keygen
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from rsa.cli import keygen
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(keygen())
|
||||
7
backend_service/venv/bin/pyrsa-priv2pub
Executable file
7
backend_service/venv/bin/pyrsa-priv2pub
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from rsa.util import private_to_public
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(private_to_public())
|
||||
7
backend_service/venv/bin/pyrsa-sign
Executable file
7
backend_service/venv/bin/pyrsa-sign
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from rsa.cli import sign
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(sign())
|
||||
7
backend_service/venv/bin/pyrsa-verify
Executable file
7
backend_service/venv/bin/pyrsa-verify
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from rsa.cli import verify
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(verify())
|
||||
1
backend_service/venv/bin/python
Symbolic link
1
backend_service/venv/bin/python
Symbolic link
@@ -0,0 +1 @@
|
||||
python3
|
||||
1
backend_service/venv/bin/python3
Symbolic link
1
backend_service/venv/bin/python3
Symbolic link
@@ -0,0 +1 @@
|
||||
/home/a/miniconda3/bin/python3
|
||||
1
backend_service/venv/bin/python3.13
Symbolic link
1
backend_service/venv/bin/python3.13
Symbolic link
@@ -0,0 +1 @@
|
||||
python3
|
||||
7
backend_service/venv/bin/tiny-agents
Executable file
7
backend_service/venv/bin/tiny-agents
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from huggingface_hub.inference._mcp.cli import app
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(app())
|
||||
7
backend_service/venv/bin/tqdm
Executable file
7
backend_service/venv/bin/tqdm
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from tqdm.cli import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/typer
Executable file
7
backend_service/venv/bin/typer
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from typer.cli import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/uvicorn
Executable file
7
backend_service/venv/bin/uvicorn
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from uvicorn.main import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/watchfiles
Executable file
7
backend_service/venv/bin/watchfiles
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from watchfiles.cli import cli
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(cli())
|
||||
7
backend_service/venv/bin/websockets
Executable file
7
backend_service/venv/bin/websockets
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from websockets.cli import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
7
backend_service/venv/bin/wsdump
Executable file
7
backend_service/venv/bin/wsdump
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/home/a/DronePlanning/backend_service/venv/bin/python3
|
||||
import sys
|
||||
from websocket._wsdump import main
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[0].endswith('.exe'):
|
||||
sys.argv[0] = sys.argv[0][:-4]
|
||||
sys.exit(main())
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
# This is a stub package designed to roughly emulate the _yaml
|
||||
# extension module, which previously existed as a standalone module
|
||||
# and has been moved into the `yaml` package namespace.
|
||||
# It does not perfectly mimic its old counterpart, but should get
|
||||
# close enough for anyone who's relying on it even when they shouldn't.
|
||||
import yaml
|
||||
|
||||
# in some circumstances, the yaml module we imoprted may be from a different version, so we need
|
||||
# to tread carefully when poking at it here (it may not have the attributes we expect)
|
||||
if not getattr(yaml, '__with_libyaml__', False):
|
||||
from sys import version_info
|
||||
|
||||
exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError
|
||||
raise exc("No module named '_yaml'")
|
||||
else:
|
||||
from yaml._yaml import *
|
||||
import warnings
|
||||
warnings.warn(
|
||||
'The _yaml extension module is now located at yaml._yaml'
|
||||
' and its location is subject to change. To use the'
|
||||
' LibYAML-based parser and emitter, import from `yaml`:'
|
||||
' `from yaml import CLoader as Loader, CDumper as Dumper`.',
|
||||
DeprecationWarning
|
||||
)
|
||||
del warnings
|
||||
# Don't `del yaml` here because yaml is actually an existing
|
||||
# namespace member of _yaml.
|
||||
|
||||
__name__ = '_yaml'
|
||||
# If the module is top-level (i.e. not a part of any specific package)
|
||||
# then the attribute should be set to ''.
|
||||
# https://docs.python.org/3.8/library/types.html
|
||||
__package__ = ''
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,145 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: annotated-doc
|
||||
Version: 0.0.4
|
||||
Summary: Document parameters, class attributes, return types, and variables inline, with Annotated.
|
||||
Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?= <tiangolo@gmail.com>
|
||||
License-Expression: MIT
|
||||
License-File: LICENSE
|
||||
Classifier: Intended Audience :: Information Technology
|
||||
Classifier: Intended Audience :: System Administrators
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Topic :: Internet
|
||||
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Classifier: Topic :: Software Development :: Libraries
|
||||
Classifier: Topic :: Software Development
|
||||
Classifier: Typing :: Typed
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Programming Language :: Python :: 3.14
|
||||
Project-URL: Homepage, https://github.com/fastapi/annotated-doc
|
||||
Project-URL: Documentation, https://github.com/fastapi/annotated-doc
|
||||
Project-URL: Repository, https://github.com/fastapi/annotated-doc
|
||||
Project-URL: Issues, https://github.com/fastapi/annotated-doc/issues
|
||||
Project-URL: Changelog, https://github.com/fastapi/annotated-doc/release-notes.md
|
||||
Requires-Python: >=3.8
|
||||
Description-Content-Type: text/markdown
|
||||
|
||||
# Annotated Doc
|
||||
|
||||
Document parameters, class attributes, return types, and variables inline, with `Annotated`.
|
||||
|
||||
<a href="https://github.com/fastapi/annotated-doc/actions?query=workflow%3ATest+event%3Apush+branch%3Amain" target="_blank">
|
||||
<img src="https://github.com/fastapi/annotated-doc/actions/workflows/test.yml/badge.svg?event=push&branch=main" alt="Test">
|
||||
</a>
|
||||
<a href="https://coverage-badge.samuelcolvin.workers.dev/redirect/fastapi/annotated-doc" target="_blank">
|
||||
<img src="https://coverage-badge.samuelcolvin.workers.dev/fastapi/annotated-doc.svg" alt="Coverage">
|
||||
</a>
|
||||
<a href="https://pypi.org/project/annotated-doc" target="_blank">
|
||||
<img src="https://img.shields.io/pypi/v/annotated-doc?color=%2334D058&label=pypi%20package" alt="Package version">
|
||||
</a>
|
||||
<a href="https://pypi.org/project/annotated-doc" target="_blank">
|
||||
<img src="https://img.shields.io/pypi/pyversions/annotated-doc.svg?color=%2334D058" alt="Supported Python versions">
|
||||
</a>
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install annotated-doc
|
||||
```
|
||||
|
||||
Or with `uv`:
|
||||
|
||||
```Python
|
||||
uv add annotated-doc
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Import `Doc` and pass a single literal string with the documentation for the specific parameter, class attribute, return type, or variable.
|
||||
|
||||
For example, to document a parameter `name` in a function `hi` you could do:
|
||||
|
||||
```Python
|
||||
from typing import Annotated
|
||||
|
||||
from annotated_doc import Doc
|
||||
|
||||
def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None:
|
||||
print(f"Hi, {name}!")
|
||||
```
|
||||
|
||||
You can also use it to document class attributes:
|
||||
|
||||
```Python
|
||||
from typing import Annotated
|
||||
|
||||
from annotated_doc import Doc
|
||||
|
||||
class User:
|
||||
name: Annotated[str, Doc("The user's name")]
|
||||
age: Annotated[int, Doc("The user's age")]
|
||||
```
|
||||
|
||||
The same way, you could document return types and variables, or anything that could have a type annotation with `Annotated`.
|
||||
|
||||
## Who Uses This
|
||||
|
||||
`annotated-doc` was made for:
|
||||
|
||||
* [FastAPI](https://fastapi.tiangolo.com/)
|
||||
* [Typer](https://typer.tiangolo.com/)
|
||||
* [SQLModel](https://sqlmodel.tiangolo.com/)
|
||||
* [Asyncer](https://asyncer.tiangolo.com/)
|
||||
|
||||
`annotated-doc` is supported by [griffe-typingdoc](https://github.com/mkdocstrings/griffe-typingdoc), which powers reference documentation like the one in the [FastAPI Reference](https://fastapi.tiangolo.com/reference/).
|
||||
|
||||
## Reasons not to use `annotated-doc`
|
||||
|
||||
You are already comfortable with one of the existing docstring formats, like:
|
||||
|
||||
* Sphinx
|
||||
* numpydoc
|
||||
* Google
|
||||
* Keras
|
||||
|
||||
Your team is already comfortable using them.
|
||||
|
||||
You prefer having the documentation about parameters all together in a docstring, separated from the code defining them.
|
||||
|
||||
You care about a specific set of users, using one specific editor, and that editor already has support for the specific docstring format you use.
|
||||
|
||||
## Reasons to use `annotated-doc`
|
||||
|
||||
* No micro-syntax to learn for newcomers, it’s **just Python** syntax.
|
||||
* **Editing** would be already fully supported by default by any editor (current or future) supporting Python syntax, including syntax errors, syntax highlighting, etc.
|
||||
* **Rendering** would be relatively straightforward to implement by static tools (tools that don't need runtime execution), as the information can be extracted from the AST they normally already create.
|
||||
* **Deduplication of information**: the name of a parameter would be defined in a single place, not duplicated inside of a docstring.
|
||||
* **Elimination** of the possibility of having **inconsistencies** when removing a parameter or class variable and **forgetting to remove** its documentation.
|
||||
* **Minimization** of the probability of adding a new parameter or class variable and **forgetting to add its documentation**.
|
||||
* **Elimination** of the possibility of having **inconsistencies** between the **name** of a parameter in the **signature** and the name in the docstring when it is renamed.
|
||||
* **Access** to the documentation string for each symbol at **runtime**, including existing (older) Python versions.
|
||||
* A more formalized way to document other symbols, like type aliases, that could use Annotated.
|
||||
* **Support** for apps using FastAPI, Typer and others.
|
||||
* **AI Accessibility**: AI tools will have an easier way understanding each parameter as the distance from documentation to parameter is much closer.
|
||||
|
||||
## History
|
||||
|
||||
I ([@tiangolo](https://github.com/tiangolo)) originally wanted for this to be part of the Python standard library (in [PEP 727](https://peps.python.org/pep-0727/)), but the proposal was withdrawn as there was a fair amount of negative feedback and opposition.
|
||||
|
||||
The conclusion was that this was better done as an external effort, in a third-party library.
|
||||
|
||||
So, here it is, with a simpler approach, as a third-party library, in a way that can be used by others, starting with FastAPI and friends.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the terms of the MIT license.
|
||||
@@ -0,0 +1,11 @@
|
||||
annotated_doc-0.0.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
annotated_doc-0.0.4.dist-info/METADATA,sha256=Irm5KJua33dY2qKKAjJ-OhKaVBVIfwFGej_dSe3Z1TU,6566
|
||||
annotated_doc-0.0.4.dist-info/RECORD,,
|
||||
annotated_doc-0.0.4.dist-info/WHEEL,sha256=9P2ygRxDrTJz3gsagc0Z96ukrxjr-LFBGOgv3AuKlCA,90
|
||||
annotated_doc-0.0.4.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
||||
annotated_doc-0.0.4.dist-info/licenses/LICENSE,sha256=__Fwd5pqy_ZavbQFwIfxzuF4ZpHkqWpANFF-SlBKDN8,1086
|
||||
annotated_doc/__init__.py,sha256=VuyxxUe80kfEyWnOrCx_Bk8hybo3aKo6RYBlkBBYW8k,52
|
||||
annotated_doc/__pycache__/__init__.cpython-313.pyc,,
|
||||
annotated_doc/__pycache__/main.cpython-313.pyc,,
|
||||
annotated_doc/main.py,sha256=5Zfvxv80SwwLqpRW73AZyZyiM4bWma9QWRbp_cgD20s,1075
|
||||
annotated_doc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
@@ -0,0 +1,4 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: pdm-backend (2.4.5)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
@@ -0,0 +1,4 @@
|
||||
[console_scripts]
|
||||
|
||||
[gui_scripts]
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2025 Sebastián Ramírez
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,3 @@
|
||||
from .main import Doc as Doc
|
||||
|
||||
__version__ = "0.0.4"
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,36 @@
|
||||
class Doc:
|
||||
"""Define the documentation of a type annotation using `Annotated`, to be
|
||||
used in class attributes, function and method parameters, return values,
|
||||
and variables.
|
||||
|
||||
The value should be a positional-only string literal to allow static tools
|
||||
like editors and documentation generators to use it.
|
||||
|
||||
This complements docstrings.
|
||||
|
||||
The string value passed is available in the attribute `documentation`.
|
||||
|
||||
Example:
|
||||
|
||||
```Python
|
||||
from typing import Annotated
|
||||
from annotated_doc import Doc
|
||||
|
||||
def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None:
|
||||
print(f"Hi, {name}!")
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(self, documentation: str, /) -> None:
|
||||
self.documentation = documentation
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"Doc({self.documentation!r})"
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.documentation)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, Doc):
|
||||
return NotImplemented
|
||||
return self.documentation == other.documentation
|
||||
@@ -0,0 +1 @@
|
||||
pip
|
||||
@@ -0,0 +1,295 @@
|
||||
Metadata-Version: 2.3
|
||||
Name: annotated-types
|
||||
Version: 0.7.0
|
||||
Summary: Reusable constraint types to use with typing.Annotated
|
||||
Project-URL: Homepage, https://github.com/annotated-types/annotated-types
|
||||
Project-URL: Source, https://github.com/annotated-types/annotated-types
|
||||
Project-URL: Changelog, https://github.com/annotated-types/annotated-types/releases
|
||||
Author-email: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>, Samuel Colvin <s@muelcolvin.com>, Zac Hatfield-Dodds <zac@zhd.dev>
|
||||
License-File: LICENSE
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Environment :: Console
|
||||
Classifier: Environment :: MacOS X
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Intended Audience :: Information Technology
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Operating System :: POSIX :: Linux
|
||||
Classifier: Operating System :: Unix
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Classifier: Typing :: Typed
|
||||
Requires-Python: >=3.8
|
||||
Requires-Dist: typing-extensions>=4.0.0; python_version < '3.9'
|
||||
Description-Content-Type: text/markdown
|
||||
|
||||
# annotated-types
|
||||
|
||||
[](https://github.com/annotated-types/annotated-types/actions?query=event%3Apush+branch%3Amain+workflow%3ACI)
|
||||
[](https://pypi.python.org/pypi/annotated-types)
|
||||
[](https://github.com/annotated-types/annotated-types)
|
||||
[](https://github.com/annotated-types/annotated-types/blob/main/LICENSE)
|
||||
|
||||
[PEP-593](https://peps.python.org/pep-0593/) added `typing.Annotated` as a way of
|
||||
adding context-specific metadata to existing types, and specifies that
|
||||
`Annotated[T, x]` _should_ be treated as `T` by any tool or library without special
|
||||
logic for `x`.
|
||||
|
||||
This package provides metadata objects which can be used to represent common
|
||||
constraints such as upper and lower bounds on scalar values and collection sizes,
|
||||
a `Predicate` marker for runtime checks, and
|
||||
descriptions of how we intend these metadata to be interpreted. In some cases,
|
||||
we also note alternative representations which do not require this package.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
pip install annotated-types
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from annotated_types import Gt, Len, Predicate
|
||||
|
||||
class MyClass:
|
||||
age: Annotated[int, Gt(18)] # Valid: 19, 20, ...
|
||||
# Invalid: 17, 18, "19", 19.0, ...
|
||||
factors: list[Annotated[int, Predicate(is_prime)]] # Valid: 2, 3, 5, 7, 11, ...
|
||||
# Invalid: 4, 8, -2, 5.0, "prime", ...
|
||||
|
||||
my_list: Annotated[list[int], Len(0, 10)] # Valid: [], [10, 20, 30, 40, 50]
|
||||
# Invalid: (1, 2), ["abc"], [0] * 20
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
_While `annotated-types` avoids runtime checks for performance, users should not
|
||||
construct invalid combinations such as `MultipleOf("non-numeric")` or `Annotated[int, Len(3)]`.
|
||||
Downstream implementors may choose to raise an error, emit a warning, silently ignore
|
||||
a metadata item, etc., if the metadata objects described below are used with an
|
||||
incompatible type - or for any other reason!_
|
||||
|
||||
### Gt, Ge, Lt, Le
|
||||
|
||||
Express inclusive and/or exclusive bounds on orderable values - which may be numbers,
|
||||
dates, times, strings, sets, etc. Note that the boundary value need not be of the
|
||||
same type that was annotated, so long as they can be compared: `Annotated[int, Gt(1.5)]`
|
||||
is fine, for example, and implies that the value is an integer x such that `x > 1.5`.
|
||||
|
||||
We suggest that implementors may also interpret `functools.partial(operator.le, 1.5)`
|
||||
as being equivalent to `Gt(1.5)`, for users who wish to avoid a runtime dependency on
|
||||
the `annotated-types` package.
|
||||
|
||||
To be explicit, these types have the following meanings:
|
||||
|
||||
* `Gt(x)` - value must be "Greater Than" `x` - equivalent to exclusive minimum
|
||||
* `Ge(x)` - value must be "Greater than or Equal" to `x` - equivalent to inclusive minimum
|
||||
* `Lt(x)` - value must be "Less Than" `x` - equivalent to exclusive maximum
|
||||
* `Le(x)` - value must be "Less than or Equal" to `x` - equivalent to inclusive maximum
|
||||
|
||||
### Interval
|
||||
|
||||
`Interval(gt, ge, lt, le)` allows you to specify an upper and lower bound with a single
|
||||
metadata object. `None` attributes should be ignored, and non-`None` attributes
|
||||
treated as per the single bounds above.
|
||||
|
||||
### MultipleOf
|
||||
|
||||
`MultipleOf(multiple_of=x)` might be interpreted in two ways:
|
||||
|
||||
1. Python semantics, implying `value % multiple_of == 0`, or
|
||||
2. [JSONschema semantics](https://json-schema.org/draft/2020-12/json-schema-validation.html#rfc.section.6.2.1),
|
||||
where `int(value / multiple_of) == value / multiple_of`.
|
||||
|
||||
We encourage users to be aware of these two common interpretations and their
|
||||
distinct behaviours, especially since very large or non-integer numbers make
|
||||
it easy to cause silent data corruption due to floating-point imprecision.
|
||||
|
||||
We encourage libraries to carefully document which interpretation they implement.
|
||||
|
||||
### MinLen, MaxLen, Len
|
||||
|
||||
`Len()` implies that `min_length <= len(value) <= max_length` - lower and upper bounds are inclusive.
|
||||
|
||||
As well as `Len()` which can optionally include upper and lower bounds, we also
|
||||
provide `MinLen(x)` and `MaxLen(y)` which are equivalent to `Len(min_length=x)`
|
||||
and `Len(max_length=y)` respectively.
|
||||
|
||||
`Len`, `MinLen`, and `MaxLen` may be used with any type which supports `len(value)`.
|
||||
|
||||
Examples of usage:
|
||||
|
||||
* `Annotated[list, MaxLen(10)]` (or `Annotated[list, Len(max_length=10))`) - list must have a length of 10 or less
|
||||
* `Annotated[str, MaxLen(10)]` - string must have a length of 10 or less
|
||||
* `Annotated[list, MinLen(3))` (or `Annotated[list, Len(min_length=3))`) - list must have a length of 3 or more
|
||||
* `Annotated[list, Len(4, 6)]` - list must have a length of 4, 5, or 6
|
||||
* `Annotated[list, Len(8, 8)]` - list must have a length of exactly 8
|
||||
|
||||
#### Changed in v0.4.0
|
||||
|
||||
* `min_inclusive` has been renamed to `min_length`, no change in meaning
|
||||
* `max_exclusive` has been renamed to `max_length`, upper bound is now **inclusive** instead of **exclusive**
|
||||
* The recommendation that slices are interpreted as `Len` has been removed due to ambiguity and different semantic
|
||||
meaning of the upper bound in slices vs. `Len`
|
||||
|
||||
See [issue #23](https://github.com/annotated-types/annotated-types/issues/23) for discussion.
|
||||
|
||||
### Timezone
|
||||
|
||||
`Timezone` can be used with a `datetime` or a `time` to express which timezones
|
||||
are allowed. `Annotated[datetime, Timezone(None)]` must be a naive datetime.
|
||||
`Timezone[...]` ([literal ellipsis](https://docs.python.org/3/library/constants.html#Ellipsis))
|
||||
expresses that any timezone-aware datetime is allowed. You may also pass a specific
|
||||
timezone string or [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects)
|
||||
object such as `Timezone(timezone.utc)` or `Timezone("Africa/Abidjan")` to express that you only
|
||||
allow a specific timezone, though we note that this is often a symptom of fragile design.
|
||||
|
||||
#### Changed in v0.x.x
|
||||
|
||||
* `Timezone` accepts [`tzinfo`](https://docs.python.org/3/library/datetime.html#tzinfo-objects) objects instead of
|
||||
`timezone`, extending compatibility to [`zoneinfo`](https://docs.python.org/3/library/zoneinfo.html) and third party libraries.
|
||||
|
||||
### Unit
|
||||
|
||||
`Unit(unit: str)` expresses that the annotated numeric value is the magnitude of
|
||||
a quantity with the specified unit. For example, `Annotated[float, Unit("m/s")]`
|
||||
would be a float representing a velocity in meters per second.
|
||||
|
||||
Please note that `annotated_types` itself makes no attempt to parse or validate
|
||||
the unit string in any way. That is left entirely to downstream libraries,
|
||||
such as [`pint`](https://pint.readthedocs.io) or
|
||||
[`astropy.units`](https://docs.astropy.org/en/stable/units/).
|
||||
|
||||
An example of how a library might use this metadata:
|
||||
|
||||
```python
|
||||
from annotated_types import Unit
|
||||
from typing import Annotated, TypeVar, Callable, Any, get_origin, get_args
|
||||
|
||||
# given a type annotated with a unit:
|
||||
Meters = Annotated[float, Unit("m")]
|
||||
|
||||
|
||||
# you can cast the annotation to a specific unit type with any
|
||||
# callable that accepts a string and returns the desired type
|
||||
T = TypeVar("T")
|
||||
def cast_unit(tp: Any, unit_cls: Callable[[str], T]) -> T | None:
|
||||
if get_origin(tp) is Annotated:
|
||||
for arg in get_args(tp):
|
||||
if isinstance(arg, Unit):
|
||||
return unit_cls(arg.unit)
|
||||
return None
|
||||
|
||||
|
||||
# using `pint`
|
||||
import pint
|
||||
pint_unit = cast_unit(Meters, pint.Unit)
|
||||
|
||||
|
||||
# using `astropy.units`
|
||||
import astropy.units as u
|
||||
astropy_unit = cast_unit(Meters, u.Unit)
|
||||
```
|
||||
|
||||
### Predicate
|
||||
|
||||
`Predicate(func: Callable)` expresses that `func(value)` is truthy for valid values.
|
||||
Users should prefer the statically inspectable metadata above, but if you need
|
||||
the full power and flexibility of arbitrary runtime predicates... here it is.
|
||||
|
||||
For some common constraints, we provide generic types:
|
||||
|
||||
* `IsLower = Annotated[T, Predicate(str.islower)]`
|
||||
* `IsUpper = Annotated[T, Predicate(str.isupper)]`
|
||||
* `IsDigit = Annotated[T, Predicate(str.isdigit)]`
|
||||
* `IsFinite = Annotated[T, Predicate(math.isfinite)]`
|
||||
* `IsNotFinite = Annotated[T, Predicate(Not(math.isfinite))]`
|
||||
* `IsNan = Annotated[T, Predicate(math.isnan)]`
|
||||
* `IsNotNan = Annotated[T, Predicate(Not(math.isnan))]`
|
||||
* `IsInfinite = Annotated[T, Predicate(math.isinf)]`
|
||||
* `IsNotInfinite = Annotated[T, Predicate(Not(math.isinf))]`
|
||||
|
||||
so that you can write e.g. `x: IsFinite[float] = 2.0` instead of the longer
|
||||
(but exactly equivalent) `x: Annotated[float, Predicate(math.isfinite)] = 2.0`.
|
||||
|
||||
Some libraries might have special logic to handle known or understandable predicates,
|
||||
for example by checking for `str.isdigit` and using its presence to both call custom
|
||||
logic to enforce digit-only strings, and customise some generated external schema.
|
||||
Users are therefore encouraged to avoid indirection like `lambda s: s.lower()`, in
|
||||
favor of introspectable methods such as `str.lower` or `re.compile("pattern").search`.
|
||||
|
||||
To enable basic negation of commonly used predicates like `math.isnan` without introducing introspection that makes it impossible for implementers to introspect the predicate we provide a `Not` wrapper that simply negates the predicate in an introspectable manner. Several of the predicates listed above are created in this manner.
|
||||
|
||||
We do not specify what behaviour should be expected for predicates that raise
|
||||
an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently
|
||||
skip invalid constraints, or statically raise an error; or it might try calling it
|
||||
and then propagate or discard the resulting
|
||||
`TypeError: descriptor 'isdigit' for 'str' objects doesn't apply to a 'int' object`
|
||||
exception. We encourage libraries to document the behaviour they choose.
|
||||
|
||||
### Doc
|
||||
|
||||
`doc()` can be used to add documentation information in `Annotated`, for function and method parameters, variables, class attributes, return types, and any place where `Annotated` can be used.
|
||||
|
||||
It expects a value that can be statically analyzed, as the main use case is for static analysis, editors, documentation generators, and similar tools.
|
||||
|
||||
It returns a `DocInfo` class with a single attribute `documentation` containing the value passed to `doc()`.
|
||||
|
||||
This is the early adopter's alternative form of the [`typing-doc` proposal](https://github.com/tiangolo/fastapi/blob/typing-doc/typing_doc.md).
|
||||
|
||||
### Integrating downstream types with `GroupedMetadata`
|
||||
|
||||
Implementers may choose to provide a convenience wrapper that groups multiple pieces of metadata.
|
||||
This can help reduce verbosity and cognitive overhead for users.
|
||||
For example, an implementer like Pydantic might provide a `Field` or `Meta` type that accepts keyword arguments and transforms these into low-level metadata:
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterator
|
||||
from annotated_types import GroupedMetadata, Ge
|
||||
|
||||
@dataclass
|
||||
class Field(GroupedMetadata):
|
||||
ge: int | None = None
|
||||
description: str | None = None
|
||||
|
||||
def __iter__(self) -> Iterator[object]:
|
||||
# Iterating over a GroupedMetadata object should yield annotated-types
|
||||
# constraint metadata objects which describe it as fully as possible,
|
||||
# and may include other unknown objects too.
|
||||
if self.ge is not None:
|
||||
yield Ge(self.ge)
|
||||
if self.description is not None:
|
||||
yield Description(self.description)
|
||||
```
|
||||
|
||||
Libraries consuming annotated-types constraints should check for `GroupedMetadata` and unpack it by iterating over the object and treating the results as if they had been "unpacked" in the `Annotated` type. The same logic should be applied to the [PEP 646 `Unpack` type](https://peps.python.org/pep-0646/), so that `Annotated[T, Field(...)]`, `Annotated[T, Unpack[Field(...)]]` and `Annotated[T, *Field(...)]` are all treated consistently.
|
||||
|
||||
Libraries consuming annotated-types should also ignore any metadata they do not recongize that came from unpacking a `GroupedMetadata`, just like they ignore unrecognized metadata in `Annotated` itself.
|
||||
|
||||
Our own `annotated_types.Interval` class is a `GroupedMetadata` which unpacks itself into `Gt`, `Lt`, etc., so this is not an abstract concern. Similarly, `annotated_types.Len` is a `GroupedMetadata` which unpacks itself into `MinLen` (optionally) and `MaxLen`.
|
||||
|
||||
### Consuming metadata
|
||||
|
||||
We intend to not be prescriptive as to _how_ the metadata and constraints are used, but as an example of how one might parse constraints from types annotations see our [implementation in `test_main.py`](https://github.com/annotated-types/annotated-types/blob/f59cf6d1b5255a0fe359b93896759a180bec30ae/tests/test_main.py#L94-L103).
|
||||
|
||||
It is up to the implementer to determine how this metadata is used.
|
||||
You could use the metadata for runtime type checking, for generating schemas or to generate example data, amongst other use cases.
|
||||
|
||||
## Design & History
|
||||
|
||||
This package was designed at the PyCon 2022 sprints by the maintainers of Pydantic
|
||||
and Hypothesis, with the goal of making it as easy as possible for end-users to
|
||||
provide more informative annotations for use by runtime libraries.
|
||||
|
||||
It is deliberately minimal, and following PEP-593 allows considerable downstream
|
||||
discretion in what (if anything!) they choose to support. Nonetheless, we expect
|
||||
that staying simple and covering _only_ the most common use-cases will give users
|
||||
and maintainers the best experience we can. If you'd like more constraints for your
|
||||
types - follow our lead, by defining them and documenting them downstream!
|
||||
@@ -0,0 +1,10 @@
|
||||
annotated_types-0.7.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
annotated_types-0.7.0.dist-info/METADATA,sha256=7ltqxksJJ0wCYFGBNIQCWTlWQGeAH0hRFdnK3CB895E,15046
|
||||
annotated_types-0.7.0.dist-info/RECORD,,
|
||||
annotated_types-0.7.0.dist-info/WHEEL,sha256=zEMcRr9Kr03x1ozGwg5v9NQBKn3kndp6LSoSlVg-jhU,87
|
||||
annotated_types-0.7.0.dist-info/licenses/LICENSE,sha256=_hBJiEsaDZNCkB6I4H8ykl0ksxIdmXK2poBfuYJLCV0,1083
|
||||
annotated_types/__init__.py,sha256=RynLsRKUEGI0KimXydlD1fZEfEzWwDo0Uon3zOKhG1Q,13819
|
||||
annotated_types/__pycache__/__init__.cpython-313.pyc,,
|
||||
annotated_types/__pycache__/test_cases.cpython-313.pyc,,
|
||||
annotated_types/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
annotated_types/test_cases.py,sha256=zHFX6EpcMbGJ8FzBYDbO56bPwx_DYIVSKbZM-4B3_lg,6421
|
||||
@@ -0,0 +1,4 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: hatchling 1.24.2
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2022 the contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,432 @@
|
||||
import math
|
||||
import sys
|
||||
import types
|
||||
from dataclasses import dataclass
|
||||
from datetime import tzinfo
|
||||
from typing import TYPE_CHECKING, Any, Callable, Iterator, Optional, SupportsFloat, SupportsIndex, TypeVar, Union
|
||||
|
||||
if sys.version_info < (3, 8):
|
||||
from typing_extensions import Protocol, runtime_checkable
|
||||
else:
|
||||
from typing import Protocol, runtime_checkable
|
||||
|
||||
if sys.version_info < (3, 9):
|
||||
from typing_extensions import Annotated, Literal
|
||||
else:
|
||||
from typing import Annotated, Literal
|
||||
|
||||
if sys.version_info < (3, 10):
|
||||
EllipsisType = type(Ellipsis)
|
||||
KW_ONLY = {}
|
||||
SLOTS = {}
|
||||
else:
|
||||
from types import EllipsisType
|
||||
|
||||
KW_ONLY = {"kw_only": True}
|
||||
SLOTS = {"slots": True}
|
||||
|
||||
|
||||
__all__ = (
|
||||
'BaseMetadata',
|
||||
'GroupedMetadata',
|
||||
'Gt',
|
||||
'Ge',
|
||||
'Lt',
|
||||
'Le',
|
||||
'Interval',
|
||||
'MultipleOf',
|
||||
'MinLen',
|
||||
'MaxLen',
|
||||
'Len',
|
||||
'Timezone',
|
||||
'Predicate',
|
||||
'LowerCase',
|
||||
'UpperCase',
|
||||
'IsDigits',
|
||||
'IsFinite',
|
||||
'IsNotFinite',
|
||||
'IsNan',
|
||||
'IsNotNan',
|
||||
'IsInfinite',
|
||||
'IsNotInfinite',
|
||||
'doc',
|
||||
'DocInfo',
|
||||
'__version__',
|
||||
)
|
||||
|
||||
__version__ = '0.7.0'
|
||||
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
# arguments that start with __ are considered
|
||||
# positional only
|
||||
# see https://peps.python.org/pep-0484/#positional-only-arguments
|
||||
|
||||
|
||||
class SupportsGt(Protocol):
|
||||
def __gt__(self: T, __other: T) -> bool:
|
||||
...
|
||||
|
||||
|
||||
class SupportsGe(Protocol):
|
||||
def __ge__(self: T, __other: T) -> bool:
|
||||
...
|
||||
|
||||
|
||||
class SupportsLt(Protocol):
|
||||
def __lt__(self: T, __other: T) -> bool:
|
||||
...
|
||||
|
||||
|
||||
class SupportsLe(Protocol):
|
||||
def __le__(self: T, __other: T) -> bool:
|
||||
...
|
||||
|
||||
|
||||
class SupportsMod(Protocol):
|
||||
def __mod__(self: T, __other: T) -> T:
|
||||
...
|
||||
|
||||
|
||||
class SupportsDiv(Protocol):
|
||||
def __div__(self: T, __other: T) -> T:
|
||||
...
|
||||
|
||||
|
||||
class BaseMetadata:
|
||||
"""Base class for all metadata.
|
||||
|
||||
This exists mainly so that implementers
|
||||
can do `isinstance(..., BaseMetadata)` while traversing field annotations.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class Gt(BaseMetadata):
|
||||
"""Gt(gt=x) implies that the value must be greater than x.
|
||||
|
||||
It can be used with any type that supports the ``>`` operator,
|
||||
including numbers, dates and times, strings, sets, and so on.
|
||||
"""
|
||||
|
||||
gt: SupportsGt
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class Ge(BaseMetadata):
|
||||
"""Ge(ge=x) implies that the value must be greater than or equal to x.
|
||||
|
||||
It can be used with any type that supports the ``>=`` operator,
|
||||
including numbers, dates and times, strings, sets, and so on.
|
||||
"""
|
||||
|
||||
ge: SupportsGe
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class Lt(BaseMetadata):
|
||||
"""Lt(lt=x) implies that the value must be less than x.
|
||||
|
||||
It can be used with any type that supports the ``<`` operator,
|
||||
including numbers, dates and times, strings, sets, and so on.
|
||||
"""
|
||||
|
||||
lt: SupportsLt
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class Le(BaseMetadata):
|
||||
"""Le(le=x) implies that the value must be less than or equal to x.
|
||||
|
||||
It can be used with any type that supports the ``<=`` operator,
|
||||
including numbers, dates and times, strings, sets, and so on.
|
||||
"""
|
||||
|
||||
le: SupportsLe
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class GroupedMetadata(Protocol):
|
||||
"""A grouping of multiple objects, like typing.Unpack.
|
||||
|
||||
`GroupedMetadata` on its own is not metadata and has no meaning.
|
||||
All of the constraints and metadata should be fully expressable
|
||||
in terms of the `BaseMetadata`'s returned by `GroupedMetadata.__iter__()`.
|
||||
|
||||
Concrete implementations should override `GroupedMetadata.__iter__()`
|
||||
to add their own metadata.
|
||||
For example:
|
||||
|
||||
>>> @dataclass
|
||||
>>> class Field(GroupedMetadata):
|
||||
>>> gt: float | None = None
|
||||
>>> description: str | None = None
|
||||
...
|
||||
>>> def __iter__(self) -> Iterable[object]:
|
||||
>>> if self.gt is not None:
|
||||
>>> yield Gt(self.gt)
|
||||
>>> if self.description is not None:
|
||||
>>> yield Description(self.gt)
|
||||
|
||||
Also see the implementation of `Interval` below for an example.
|
||||
|
||||
Parsers should recognize this and unpack it so that it can be used
|
||||
both with and without unpacking:
|
||||
|
||||
- `Annotated[int, Field(...)]` (parser must unpack Field)
|
||||
- `Annotated[int, *Field(...)]` (PEP-646)
|
||||
""" # noqa: trailing-whitespace
|
||||
|
||||
@property
|
||||
def __is_annotated_types_grouped_metadata__(self) -> Literal[True]:
|
||||
return True
|
||||
|
||||
def __iter__(self) -> Iterator[object]:
|
||||
...
|
||||
|
||||
if not TYPE_CHECKING:
|
||||
__slots__ = () # allow subclasses to use slots
|
||||
|
||||
def __init_subclass__(cls, *args: Any, **kwargs: Any) -> None:
|
||||
# Basic ABC like functionality without the complexity of an ABC
|
||||
super().__init_subclass__(*args, **kwargs)
|
||||
if cls.__iter__ is GroupedMetadata.__iter__:
|
||||
raise TypeError("Can't subclass GroupedMetadata without implementing __iter__")
|
||||
|
||||
def __iter__(self) -> Iterator[object]: # noqa: F811
|
||||
raise NotImplementedError # more helpful than "None has no attribute..." type errors
|
||||
|
||||
|
||||
@dataclass(frozen=True, **KW_ONLY, **SLOTS)
|
||||
class Interval(GroupedMetadata):
|
||||
"""Interval can express inclusive or exclusive bounds with a single object.
|
||||
|
||||
It accepts keyword arguments ``gt``, ``ge``, ``lt``, and/or ``le``, which
|
||||
are interpreted the same way as the single-bound constraints.
|
||||
"""
|
||||
|
||||
gt: Union[SupportsGt, None] = None
|
||||
ge: Union[SupportsGe, None] = None
|
||||
lt: Union[SupportsLt, None] = None
|
||||
le: Union[SupportsLe, None] = None
|
||||
|
||||
def __iter__(self) -> Iterator[BaseMetadata]:
|
||||
"""Unpack an Interval into zero or more single-bounds."""
|
||||
if self.gt is not None:
|
||||
yield Gt(self.gt)
|
||||
if self.ge is not None:
|
||||
yield Ge(self.ge)
|
||||
if self.lt is not None:
|
||||
yield Lt(self.lt)
|
||||
if self.le is not None:
|
||||
yield Le(self.le)
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class MultipleOf(BaseMetadata):
|
||||
"""MultipleOf(multiple_of=x) might be interpreted in two ways:
|
||||
|
||||
1. Python semantics, implying ``value % multiple_of == 0``, or
|
||||
2. JSONschema semantics, where ``int(value / multiple_of) == value / multiple_of``
|
||||
|
||||
We encourage users to be aware of these two common interpretations,
|
||||
and libraries to carefully document which they implement.
|
||||
"""
|
||||
|
||||
multiple_of: Union[SupportsDiv, SupportsMod]
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class MinLen(BaseMetadata):
|
||||
"""
|
||||
MinLen() implies minimum inclusive length,
|
||||
e.g. ``len(value) >= min_length``.
|
||||
"""
|
||||
|
||||
min_length: Annotated[int, Ge(0)]
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class MaxLen(BaseMetadata):
|
||||
"""
|
||||
MaxLen() implies maximum inclusive length,
|
||||
e.g. ``len(value) <= max_length``.
|
||||
"""
|
||||
|
||||
max_length: Annotated[int, Ge(0)]
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class Len(GroupedMetadata):
|
||||
"""
|
||||
Len() implies that ``min_length <= len(value) <= max_length``.
|
||||
|
||||
Upper bound may be omitted or ``None`` to indicate no upper length bound.
|
||||
"""
|
||||
|
||||
min_length: Annotated[int, Ge(0)] = 0
|
||||
max_length: Optional[Annotated[int, Ge(0)]] = None
|
||||
|
||||
def __iter__(self) -> Iterator[BaseMetadata]:
|
||||
"""Unpack a Len into zone or more single-bounds."""
|
||||
if self.min_length > 0:
|
||||
yield MinLen(self.min_length)
|
||||
if self.max_length is not None:
|
||||
yield MaxLen(self.max_length)
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class Timezone(BaseMetadata):
|
||||
"""Timezone(tz=...) requires a datetime to be aware (or ``tz=None``, naive).
|
||||
|
||||
``Annotated[datetime, Timezone(None)]`` must be a naive datetime.
|
||||
``Timezone[...]`` (the ellipsis literal) expresses that the datetime must be
|
||||
tz-aware but any timezone is allowed.
|
||||
|
||||
You may also pass a specific timezone string or tzinfo object such as
|
||||
``Timezone(timezone.utc)`` or ``Timezone("Africa/Abidjan")`` to express that
|
||||
you only allow a specific timezone, though we note that this is often
|
||||
a symptom of poor design.
|
||||
"""
|
||||
|
||||
tz: Union[str, tzinfo, EllipsisType, None]
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class Unit(BaseMetadata):
|
||||
"""Indicates that the value is a physical quantity with the specified unit.
|
||||
|
||||
It is intended for usage with numeric types, where the value represents the
|
||||
magnitude of the quantity. For example, ``distance: Annotated[float, Unit('m')]``
|
||||
or ``speed: Annotated[float, Unit('m/s')]``.
|
||||
|
||||
Interpretation of the unit string is left to the discretion of the consumer.
|
||||
It is suggested to follow conventions established by python libraries that work
|
||||
with physical quantities, such as
|
||||
|
||||
- ``pint`` : <https://pint.readthedocs.io/en/stable/>
|
||||
- ``astropy.units``: <https://docs.astropy.org/en/stable/units/>
|
||||
|
||||
For indicating a quantity with a certain dimensionality but without a specific unit
|
||||
it is recommended to use square brackets, e.g. `Annotated[float, Unit('[time]')]`.
|
||||
Note, however, ``annotated_types`` itself makes no use of the unit string.
|
||||
"""
|
||||
|
||||
unit: str
|
||||
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class Predicate(BaseMetadata):
|
||||
"""``Predicate(func: Callable)`` implies `func(value)` is truthy for valid values.
|
||||
|
||||
Users should prefer statically inspectable metadata, but if you need the full
|
||||
power and flexibility of arbitrary runtime predicates... here it is.
|
||||
|
||||
We provide a few predefined predicates for common string constraints:
|
||||
``IsLower = Predicate(str.islower)``, ``IsUpper = Predicate(str.isupper)``, and
|
||||
``IsDigits = Predicate(str.isdigit)``. Users are encouraged to use methods which
|
||||
can be given special handling, and avoid indirection like ``lambda s: s.lower()``.
|
||||
|
||||
Some libraries might have special logic to handle certain predicates, e.g. by
|
||||
checking for `str.isdigit` and using its presence to both call custom logic to
|
||||
enforce digit-only strings, and customise some generated external schema.
|
||||
|
||||
We do not specify what behaviour should be expected for predicates that raise
|
||||
an exception. For example `Annotated[int, Predicate(str.isdigit)]` might silently
|
||||
skip invalid constraints, or statically raise an error; or it might try calling it
|
||||
and then propagate or discard the resulting exception.
|
||||
"""
|
||||
|
||||
func: Callable[[Any], bool]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
if getattr(self.func, "__name__", "<lambda>") == "<lambda>":
|
||||
return f"{self.__class__.__name__}({self.func!r})"
|
||||
if isinstance(self.func, (types.MethodType, types.BuiltinMethodType)) and (
|
||||
namespace := getattr(self.func.__self__, "__name__", None)
|
||||
):
|
||||
return f"{self.__class__.__name__}({namespace}.{self.func.__name__})"
|
||||
if isinstance(self.func, type(str.isascii)): # method descriptor
|
||||
return f"{self.__class__.__name__}({self.func.__qualname__})"
|
||||
return f"{self.__class__.__name__}({self.func.__name__})"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Not:
|
||||
func: Callable[[Any], bool]
|
||||
|
||||
def __call__(self, __v: Any) -> bool:
|
||||
return not self.func(__v)
|
||||
|
||||
|
||||
_StrType = TypeVar("_StrType", bound=str)
|
||||
|
||||
LowerCase = Annotated[_StrType, Predicate(str.islower)]
|
||||
"""
|
||||
Return True if the string is a lowercase string, False otherwise.
|
||||
|
||||
A string is lowercase if all cased characters in the string are lowercase and there is at least one cased character in the string.
|
||||
""" # noqa: E501
|
||||
UpperCase = Annotated[_StrType, Predicate(str.isupper)]
|
||||
"""
|
||||
Return True if the string is an uppercase string, False otherwise.
|
||||
|
||||
A string is uppercase if all cased characters in the string are uppercase and there is at least one cased character in the string.
|
||||
""" # noqa: E501
|
||||
IsDigit = Annotated[_StrType, Predicate(str.isdigit)]
|
||||
IsDigits = IsDigit # type: ignore # plural for backwards compatibility, see #63
|
||||
"""
|
||||
Return True if the string is a digit string, False otherwise.
|
||||
|
||||
A string is a digit string if all characters in the string are digits and there is at least one character in the string.
|
||||
""" # noqa: E501
|
||||
IsAscii = Annotated[_StrType, Predicate(str.isascii)]
|
||||
"""
|
||||
Return True if all characters in the string are ASCII, False otherwise.
|
||||
|
||||
ASCII characters have code points in the range U+0000-U+007F. Empty string is ASCII too.
|
||||
"""
|
||||
|
||||
_NumericType = TypeVar('_NumericType', bound=Union[SupportsFloat, SupportsIndex])
|
||||
IsFinite = Annotated[_NumericType, Predicate(math.isfinite)]
|
||||
"""Return True if x is neither an infinity nor a NaN, and False otherwise."""
|
||||
IsNotFinite = Annotated[_NumericType, Predicate(Not(math.isfinite))]
|
||||
"""Return True if x is one of infinity or NaN, and False otherwise"""
|
||||
IsNan = Annotated[_NumericType, Predicate(math.isnan)]
|
||||
"""Return True if x is a NaN (not a number), and False otherwise."""
|
||||
IsNotNan = Annotated[_NumericType, Predicate(Not(math.isnan))]
|
||||
"""Return True if x is anything but NaN (not a number), and False otherwise."""
|
||||
IsInfinite = Annotated[_NumericType, Predicate(math.isinf)]
|
||||
"""Return True if x is a positive or negative infinity, and False otherwise."""
|
||||
IsNotInfinite = Annotated[_NumericType, Predicate(Not(math.isinf))]
|
||||
"""Return True if x is neither a positive or negative infinity, and False otherwise."""
|
||||
|
||||
try:
|
||||
from typing_extensions import DocInfo, doc # type: ignore [attr-defined]
|
||||
except ImportError:
|
||||
|
||||
@dataclass(frozen=True, **SLOTS)
|
||||
class DocInfo: # type: ignore [no-redef]
|
||||
""" "
|
||||
The return value of doc(), mainly to be used by tools that want to extract the
|
||||
Annotated documentation at runtime.
|
||||
"""
|
||||
|
||||
documentation: str
|
||||
"""The documentation string passed to doc()."""
|
||||
|
||||
def doc(
|
||||
documentation: str,
|
||||
) -> DocInfo:
|
||||
"""
|
||||
Add documentation to a type annotation inside of Annotated.
|
||||
|
||||
For example:
|
||||
|
||||
>>> def hi(name: Annotated[int, doc("The name of the user")]) -> None: ...
|
||||
"""
|
||||
return DocInfo(documentation)
|
||||
Binary file not shown.
@@ -0,0 +1,151 @@
|
||||
import math
|
||||
import sys
|
||||
from datetime import date, datetime, timedelta, timezone
|
||||
from decimal import Decimal
|
||||
from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Set, Tuple
|
||||
|
||||
if sys.version_info < (3, 9):
|
||||
from typing_extensions import Annotated
|
||||
else:
|
||||
from typing import Annotated
|
||||
|
||||
import annotated_types as at
|
||||
|
||||
|
||||
class Case(NamedTuple):
|
||||
"""
|
||||
A test case for `annotated_types`.
|
||||
"""
|
||||
|
||||
annotation: Any
|
||||
valid_cases: Iterable[Any]
|
||||
invalid_cases: Iterable[Any]
|
||||
|
||||
|
||||
def cases() -> Iterable[Case]:
|
||||
# Gt, Ge, Lt, Le
|
||||
yield Case(Annotated[int, at.Gt(4)], (5, 6, 1000), (4, 0, -1))
|
||||
yield Case(Annotated[float, at.Gt(0.5)], (0.6, 0.7, 0.8, 0.9), (0.5, 0.0, -0.1))
|
||||
yield Case(
|
||||
Annotated[datetime, at.Gt(datetime(2000, 1, 1))],
|
||||
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||
[datetime(2000, 1, 1), datetime(1999, 12, 31)],
|
||||
)
|
||||
yield Case(
|
||||
Annotated[datetime, at.Gt(date(2000, 1, 1))],
|
||||
[date(2000, 1, 2), date(2000, 1, 3)],
|
||||
[date(2000, 1, 1), date(1999, 12, 31)],
|
||||
)
|
||||
yield Case(
|
||||
Annotated[datetime, at.Gt(Decimal('1.123'))],
|
||||
[Decimal('1.1231'), Decimal('123')],
|
||||
[Decimal('1.123'), Decimal('0')],
|
||||
)
|
||||
|
||||
yield Case(Annotated[int, at.Ge(4)], (4, 5, 6, 1000, 4), (0, -1))
|
||||
yield Case(Annotated[float, at.Ge(0.5)], (0.5, 0.6, 0.7, 0.8, 0.9), (0.4, 0.0, -0.1))
|
||||
yield Case(
|
||||
Annotated[datetime, at.Ge(datetime(2000, 1, 1))],
|
||||
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||
[datetime(1998, 1, 1), datetime(1999, 12, 31)],
|
||||
)
|
||||
|
||||
yield Case(Annotated[int, at.Lt(4)], (0, -1), (4, 5, 6, 1000, 4))
|
||||
yield Case(Annotated[float, at.Lt(0.5)], (0.4, 0.0, -0.1), (0.5, 0.6, 0.7, 0.8, 0.9))
|
||||
yield Case(
|
||||
Annotated[datetime, at.Lt(datetime(2000, 1, 1))],
|
||||
[datetime(1999, 12, 31), datetime(1999, 12, 31)],
|
||||
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||
)
|
||||
|
||||
yield Case(Annotated[int, at.Le(4)], (4, 0, -1), (5, 6, 1000))
|
||||
yield Case(Annotated[float, at.Le(0.5)], (0.5, 0.0, -0.1), (0.6, 0.7, 0.8, 0.9))
|
||||
yield Case(
|
||||
Annotated[datetime, at.Le(datetime(2000, 1, 1))],
|
||||
[datetime(2000, 1, 1), datetime(1999, 12, 31)],
|
||||
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||
)
|
||||
|
||||
# Interval
|
||||
yield Case(Annotated[int, at.Interval(gt=4)], (5, 6, 1000), (4, 0, -1))
|
||||
yield Case(Annotated[int, at.Interval(gt=4, lt=10)], (5, 6), (4, 10, 1000, 0, -1))
|
||||
yield Case(Annotated[float, at.Interval(ge=0.5, le=1)], (0.5, 0.9, 1), (0.49, 1.1))
|
||||
yield Case(
|
||||
Annotated[datetime, at.Interval(gt=datetime(2000, 1, 1), le=datetime(2000, 1, 3))],
|
||||
[datetime(2000, 1, 2), datetime(2000, 1, 3)],
|
||||
[datetime(2000, 1, 1), datetime(2000, 1, 4)],
|
||||
)
|
||||
|
||||
yield Case(Annotated[int, at.MultipleOf(multiple_of=3)], (0, 3, 9), (1, 2, 4))
|
||||
yield Case(Annotated[float, at.MultipleOf(multiple_of=0.5)], (0, 0.5, 1, 1.5), (0.4, 1.1))
|
||||
|
||||
# lengths
|
||||
|
||||
yield Case(Annotated[str, at.MinLen(3)], ('123', '1234', 'x' * 10), ('', '1', '12'))
|
||||
yield Case(Annotated[str, at.Len(3)], ('123', '1234', 'x' * 10), ('', '1', '12'))
|
||||
yield Case(Annotated[List[int], at.MinLen(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2]))
|
||||
yield Case(Annotated[List[int], at.Len(3)], ([1, 2, 3], [1, 2, 3, 4], [1] * 10), ([], [1], [1, 2]))
|
||||
|
||||
yield Case(Annotated[str, at.MaxLen(4)], ('', '1234'), ('12345', 'x' * 10))
|
||||
yield Case(Annotated[str, at.Len(0, 4)], ('', '1234'), ('12345', 'x' * 10))
|
||||
yield Case(Annotated[List[str], at.MaxLen(4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10))
|
||||
yield Case(Annotated[List[str], at.Len(0, 4)], ([], ['a', 'bcdef'], ['a', 'b', 'c']), (['a'] * 5, ['b'] * 10))
|
||||
|
||||
yield Case(Annotated[str, at.Len(3, 5)], ('123', '12345'), ('', '1', '12', '123456', 'x' * 10))
|
||||
yield Case(Annotated[str, at.Len(3, 3)], ('123',), ('12', '1234'))
|
||||
|
||||
yield Case(Annotated[Dict[int, int], at.Len(2, 3)], [{1: 1, 2: 2}], [{}, {1: 1}, {1: 1, 2: 2, 3: 3, 4: 4}])
|
||||
yield Case(Annotated[Set[int], at.Len(2, 3)], ({1, 2}, {1, 2, 3}), (set(), {1}, {1, 2, 3, 4}))
|
||||
yield Case(Annotated[Tuple[int, ...], at.Len(2, 3)], ((1, 2), (1, 2, 3)), ((), (1,), (1, 2, 3, 4)))
|
||||
|
||||
# Timezone
|
||||
|
||||
yield Case(
|
||||
Annotated[datetime, at.Timezone(None)], [datetime(2000, 1, 1)], [datetime(2000, 1, 1, tzinfo=timezone.utc)]
|
||||
)
|
||||
yield Case(
|
||||
Annotated[datetime, at.Timezone(...)], [datetime(2000, 1, 1, tzinfo=timezone.utc)], [datetime(2000, 1, 1)]
|
||||
)
|
||||
yield Case(
|
||||
Annotated[datetime, at.Timezone(timezone.utc)],
|
||||
[datetime(2000, 1, 1, tzinfo=timezone.utc)],
|
||||
[datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))],
|
||||
)
|
||||
yield Case(
|
||||
Annotated[datetime, at.Timezone('Europe/London')],
|
||||
[datetime(2000, 1, 1, tzinfo=timezone(timedelta(0), name='Europe/London'))],
|
||||
[datetime(2000, 1, 1), datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=6)))],
|
||||
)
|
||||
|
||||
# Quantity
|
||||
|
||||
yield Case(Annotated[float, at.Unit(unit='m')], (5, 4.2), ('5m', '4.2m'))
|
||||
|
||||
# predicate types
|
||||
|
||||
yield Case(at.LowerCase[str], ['abc', 'foobar'], ['', 'A', 'Boom'])
|
||||
yield Case(at.UpperCase[str], ['ABC', 'DEFO'], ['', 'a', 'abc', 'AbC'])
|
||||
yield Case(at.IsDigit[str], ['123'], ['', 'ab', 'a1b2'])
|
||||
yield Case(at.IsAscii[str], ['123', 'foo bar'], ['£100', '😊', 'whatever 👀'])
|
||||
|
||||
yield Case(Annotated[int, at.Predicate(lambda x: x % 2 == 0)], [0, 2, 4], [1, 3, 5])
|
||||
|
||||
yield Case(at.IsFinite[float], [1.23], [math.nan, math.inf, -math.inf])
|
||||
yield Case(at.IsNotFinite[float], [math.nan, math.inf], [1.23])
|
||||
yield Case(at.IsNan[float], [math.nan], [1.23, math.inf])
|
||||
yield Case(at.IsNotNan[float], [1.23, math.inf], [math.nan])
|
||||
yield Case(at.IsInfinite[float], [math.inf], [math.nan, 1.23])
|
||||
yield Case(at.IsNotInfinite[float], [math.nan, 1.23], [math.inf])
|
||||
|
||||
# check stacked predicates
|
||||
yield Case(at.IsInfinite[Annotated[float, at.Predicate(lambda x: x > 0)]], [math.inf], [-math.inf, 1.23, math.nan])
|
||||
|
||||
# doc
|
||||
yield Case(Annotated[int, at.doc("A number")], [1, 2], [])
|
||||
|
||||
# custom GroupedMetadata
|
||||
class MyCustomGroupedMetadata(at.GroupedMetadata):
|
||||
def __iter__(self) -> Iterator[at.Predicate]:
|
||||
yield at.Predicate(lambda x: float(x).is_integer())
|
||||
|
||||
yield Case(Annotated[float, MyCustomGroupedMetadata()], [0, 2.0], [0.01, 1.5])
|
||||
@@ -0,0 +1,111 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ._core._contextmanagers import AsyncContextManagerMixin as AsyncContextManagerMixin
|
||||
from ._core._contextmanagers import ContextManagerMixin as ContextManagerMixin
|
||||
from ._core._eventloop import current_time as current_time
|
||||
from ._core._eventloop import get_all_backends as get_all_backends
|
||||
from ._core._eventloop import get_available_backends as get_available_backends
|
||||
from ._core._eventloop import get_cancelled_exc_class as get_cancelled_exc_class
|
||||
from ._core._eventloop import run as run
|
||||
from ._core._eventloop import sleep as sleep
|
||||
from ._core._eventloop import sleep_forever as sleep_forever
|
||||
from ._core._eventloop import sleep_until as sleep_until
|
||||
from ._core._exceptions import BrokenResourceError as BrokenResourceError
|
||||
from ._core._exceptions import BrokenWorkerInterpreter as BrokenWorkerInterpreter
|
||||
from ._core._exceptions import BrokenWorkerProcess as BrokenWorkerProcess
|
||||
from ._core._exceptions import BusyResourceError as BusyResourceError
|
||||
from ._core._exceptions import ClosedResourceError as ClosedResourceError
|
||||
from ._core._exceptions import ConnectionFailed as ConnectionFailed
|
||||
from ._core._exceptions import DelimiterNotFound as DelimiterNotFound
|
||||
from ._core._exceptions import EndOfStream as EndOfStream
|
||||
from ._core._exceptions import IncompleteRead as IncompleteRead
|
||||
from ._core._exceptions import NoEventLoopError as NoEventLoopError
|
||||
from ._core._exceptions import RunFinishedError as RunFinishedError
|
||||
from ._core._exceptions import TypedAttributeLookupError as TypedAttributeLookupError
|
||||
from ._core._exceptions import WouldBlock as WouldBlock
|
||||
from ._core._fileio import AsyncFile as AsyncFile
|
||||
from ._core._fileio import Path as Path
|
||||
from ._core._fileio import open_file as open_file
|
||||
from ._core._fileio import wrap_file as wrap_file
|
||||
from ._core._resources import aclose_forcefully as aclose_forcefully
|
||||
from ._core._signals import open_signal_receiver as open_signal_receiver
|
||||
from ._core._sockets import TCPConnectable as TCPConnectable
|
||||
from ._core._sockets import UNIXConnectable as UNIXConnectable
|
||||
from ._core._sockets import as_connectable as as_connectable
|
||||
from ._core._sockets import connect_tcp as connect_tcp
|
||||
from ._core._sockets import connect_unix as connect_unix
|
||||
from ._core._sockets import create_connected_udp_socket as create_connected_udp_socket
|
||||
from ._core._sockets import (
|
||||
create_connected_unix_datagram_socket as create_connected_unix_datagram_socket,
|
||||
)
|
||||
from ._core._sockets import create_tcp_listener as create_tcp_listener
|
||||
from ._core._sockets import create_udp_socket as create_udp_socket
|
||||
from ._core._sockets import create_unix_datagram_socket as create_unix_datagram_socket
|
||||
from ._core._sockets import create_unix_listener as create_unix_listener
|
||||
from ._core._sockets import getaddrinfo as getaddrinfo
|
||||
from ._core._sockets import getnameinfo as getnameinfo
|
||||
from ._core._sockets import notify_closing as notify_closing
|
||||
from ._core._sockets import wait_readable as wait_readable
|
||||
from ._core._sockets import wait_socket_readable as wait_socket_readable
|
||||
from ._core._sockets import wait_socket_writable as wait_socket_writable
|
||||
from ._core._sockets import wait_writable as wait_writable
|
||||
from ._core._streams import create_memory_object_stream as create_memory_object_stream
|
||||
from ._core._subprocesses import open_process as open_process
|
||||
from ._core._subprocesses import run_process as run_process
|
||||
from ._core._synchronization import CapacityLimiter as CapacityLimiter
|
||||
from ._core._synchronization import (
|
||||
CapacityLimiterStatistics as CapacityLimiterStatistics,
|
||||
)
|
||||
from ._core._synchronization import Condition as Condition
|
||||
from ._core._synchronization import ConditionStatistics as ConditionStatistics
|
||||
from ._core._synchronization import Event as Event
|
||||
from ._core._synchronization import EventStatistics as EventStatistics
|
||||
from ._core._synchronization import Lock as Lock
|
||||
from ._core._synchronization import LockStatistics as LockStatistics
|
||||
from ._core._synchronization import ResourceGuard as ResourceGuard
|
||||
from ._core._synchronization import Semaphore as Semaphore
|
||||
from ._core._synchronization import SemaphoreStatistics as SemaphoreStatistics
|
||||
from ._core._tasks import TASK_STATUS_IGNORED as TASK_STATUS_IGNORED
|
||||
from ._core._tasks import CancelScope as CancelScope
|
||||
from ._core._tasks import create_task_group as create_task_group
|
||||
from ._core._tasks import current_effective_deadline as current_effective_deadline
|
||||
from ._core._tasks import fail_after as fail_after
|
||||
from ._core._tasks import move_on_after as move_on_after
|
||||
from ._core._tempfile import NamedTemporaryFile as NamedTemporaryFile
|
||||
from ._core._tempfile import SpooledTemporaryFile as SpooledTemporaryFile
|
||||
from ._core._tempfile import TemporaryDirectory as TemporaryDirectory
|
||||
from ._core._tempfile import TemporaryFile as TemporaryFile
|
||||
from ._core._tempfile import gettempdir as gettempdir
|
||||
from ._core._tempfile import gettempdirb as gettempdirb
|
||||
from ._core._tempfile import mkdtemp as mkdtemp
|
||||
from ._core._tempfile import mkstemp as mkstemp
|
||||
from ._core._testing import TaskInfo as TaskInfo
|
||||
from ._core._testing import get_current_task as get_current_task
|
||||
from ._core._testing import get_running_tasks as get_running_tasks
|
||||
from ._core._testing import wait_all_tasks_blocked as wait_all_tasks_blocked
|
||||
from ._core._typedattr import TypedAttributeProvider as TypedAttributeProvider
|
||||
from ._core._typedattr import TypedAttributeSet as TypedAttributeSet
|
||||
from ._core._typedattr import typed_attribute as typed_attribute
|
||||
|
||||
# Re-export imports so they look like they live directly in this package
|
||||
for __value in list(locals().values()):
|
||||
if getattr(__value, "__module__", "").startswith("anyio."):
|
||||
__value.__module__ = __name__
|
||||
|
||||
|
||||
del __value
|
||||
|
||||
|
||||
def __getattr__(attr: str) -> type[BrokenWorkerInterpreter]:
|
||||
"""Support deprecated aliases."""
|
||||
if attr == "BrokenWorkerIntepreter":
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"The 'BrokenWorkerIntepreter' alias is deprecated, use 'BrokenWorkerInterpreter' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return BrokenWorkerInterpreter
|
||||
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {attr!r}")
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user