acled 0.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- acled/__init__.py +1 -0
- acled/clients/__init__.py +3 -0
- acled/clients/acled_data_client.py +223 -0
- acled/clients/actor_client.py +113 -0
- acled/clients/actor_type_client.py +117 -0
- acled/clients/base_http_client.py +54 -0
- acled/clients/client.py +71 -0
- acled/clients/country_client.py +121 -0
- acled/clients/deleted_client.py +4 -0
- acled/clients/region_client.py +117 -0
- acled/config.py +0 -0
- acled/exceptions.py +8 -0
- acled/log.py +43 -0
- acled/models/__init__.py +2 -0
- acled/models/data_models.py +65 -0
- acled/models/enums.py +56 -0
- acled-0.1.4.dist-info/LICENSE.md +617 -0
- acled-0.1.4.dist-info/METADATA +114 -0
- acled-0.1.4.dist-info/RECORD +20 -0
- acled-0.1.4.dist-info/WHEEL +4 -0
acled/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .clients import AcledClient
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional, Union
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
from datetime import datetime, date
|
|
5
|
+
|
|
6
|
+
from .base_http_client import BaseHttpClient
|
|
7
|
+
from ..models.data_models import AcledEvent
|
|
8
|
+
from ..models.enums import ExportType
|
|
9
|
+
from ..exceptions import ApiError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AcledDataClient(BaseHttpClient):
|
|
13
|
+
"""
|
|
14
|
+
Client for interacting with the ACLED main dataset endpoint.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, api_key: str, email: str):
|
|
18
|
+
super().__init__(api_key, email)
|
|
19
|
+
self.endpoint = "/acled/read"
|
|
20
|
+
|
|
21
|
+
def get_data(
|
|
22
|
+
self,
|
|
23
|
+
event_id_cnty: Optional[str] = None,
|
|
24
|
+
event_date: Optional[Union[str, date]] = None,
|
|
25
|
+
year: Optional[int] = None,
|
|
26
|
+
time_precision: Optional[int] = None,
|
|
27
|
+
disorder_type: Optional[str] = None,
|
|
28
|
+
event_type: Optional[str] = None,
|
|
29
|
+
sub_event_type: Optional[str] = None,
|
|
30
|
+
actor1: Optional[str] = None,
|
|
31
|
+
assoc_actor_1: Optional[str] = None,
|
|
32
|
+
inter1: Optional[int] = None,
|
|
33
|
+
actor2: Optional[str] = None,
|
|
34
|
+
assoc_actor_2: Optional[str] = None,
|
|
35
|
+
inter2: Optional[int] = None,
|
|
36
|
+
interaction: Optional[int] = None,
|
|
37
|
+
civilian_targeting: Optional[str] = None,
|
|
38
|
+
iso: Optional[int] = None,
|
|
39
|
+
region: Optional[int] = None,
|
|
40
|
+
country: Optional[str] = None,
|
|
41
|
+
admin1: Optional[str] = None,
|
|
42
|
+
admin2: Optional[str] = None,
|
|
43
|
+
admin3: Optional[str] = None,
|
|
44
|
+
location: Optional[str] = None,
|
|
45
|
+
latitude: Optional[float] = None,
|
|
46
|
+
longitude: Optional[float] = None,
|
|
47
|
+
geo_precision: Optional[int] = None,
|
|
48
|
+
source: Optional[str] = None,
|
|
49
|
+
source_scale: Optional[str] = None,
|
|
50
|
+
notes: Optional[str] = None,
|
|
51
|
+
fatalities: Optional[int] = None,
|
|
52
|
+
timestamp: Optional[Union[int, str, date]] = None,
|
|
53
|
+
export_type: Optional[str | ExportType] = ExportType.JSON,
|
|
54
|
+
limit: int = 50,
|
|
55
|
+
page: Optional[int] = None,
|
|
56
|
+
query_params: Optional[Dict[str, Any]] = None,
|
|
57
|
+
) -> List[AcledEvent]:
|
|
58
|
+
"""
|
|
59
|
+
Retrieves ACLED data based on the provided filters.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
event_id_cnty (Optional[str]): Filter by event ID country (supports LIKE).
|
|
63
|
+
event_date (Optional[Union[str, date]]): Filter by event date (format 'yyyy-mm-dd').
|
|
64
|
+
year (Optional[int]): Filter by year.
|
|
65
|
+
time_precision (Optional[int]): Filter by time precision.
|
|
66
|
+
disorder_type (Optional[str]): Filter by disorder type (supports LIKE).
|
|
67
|
+
event_type (Optional[str]): Filter by event type (supports LIKE).
|
|
68
|
+
sub_event_type (Optional[str]): Filter by sub-event type (supports LIKE).
|
|
69
|
+
actor1 (Optional[str]): Filter by actor1 (supports LIKE).
|
|
70
|
+
assoc_actor_1 (Optional[str]): Filter by associated actor1 (supports LIKE).
|
|
71
|
+
inter1 (Optional[int]): Filter by inter1 code.
|
|
72
|
+
actor2 (Optional[str]): Filter by actor2 (supports LIKE).
|
|
73
|
+
assoc_actor_2 (Optional[str]): Filter by associated actor2 (supports LIKE).
|
|
74
|
+
inter2 (Optional[int]): Filter by inter2 code.
|
|
75
|
+
interaction (Optional[int]): Filter by interaction code.
|
|
76
|
+
civilian_targeting (Optional[str]): Filter by civilian targeting (supports LIKE).
|
|
77
|
+
iso (Optional[int]): Filter by ISO country code.
|
|
78
|
+
region (Optional[int]): Filter by region number.
|
|
79
|
+
country (Optional[str]): Filter by country name.
|
|
80
|
+
admin1 (Optional[str]): Filter by admin1 (supports LIKE).
|
|
81
|
+
admin2 (Optional[str]): Filter by admin2 (supports LIKE).
|
|
82
|
+
admin3 (Optional[str]): Filter by admin3 (supports LIKE).
|
|
83
|
+
location (Optional[str]): Filter by location (supports LIKE).
|
|
84
|
+
latitude (Optional[float]): Filter by latitude.
|
|
85
|
+
longitude (Optional[float]): Filter by longitude.
|
|
86
|
+
geo_precision (Optional[int]): Filter by geographic precision.
|
|
87
|
+
source (Optional[str]): Filter by source (supports LIKE).
|
|
88
|
+
source_scale (Optional[str]): Filter by source scale (supports LIKE).
|
|
89
|
+
notes (Optional[str]): Filter by notes (supports LIKE).
|
|
90
|
+
fatalities (Optional[int]): Filter by number of fatalities.
|
|
91
|
+
timestamp (Optional[Union[int, str, date]]): Filter by timestamp (>= value).
|
|
92
|
+
export_type (Optional[str]): Specify the export type ('json', 'xml', 'csv', etc.).
|
|
93
|
+
query_params (Optional[Dict[str, Any]]): Additional query parameters (e.g., to use '_where' suffix).
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
List[AcledEvent]: A list of ACLED events matching the filters.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
ApiError: If there's an error with the API request or response.
|
|
100
|
+
"""
|
|
101
|
+
params: Dict[str, Any] = query_params.copy() if query_params else {}
|
|
102
|
+
|
|
103
|
+
# Map arguments to query parameters, handling type conversions
|
|
104
|
+
if event_id_cnty is not None:
|
|
105
|
+
params['event_id_cnty'] = event_id_cnty
|
|
106
|
+
if event_date is not None:
|
|
107
|
+
if isinstance(event_date, date):
|
|
108
|
+
event_date_str = event_date.strftime('%Y-%m-%d')
|
|
109
|
+
else:
|
|
110
|
+
event_date_str = event_date
|
|
111
|
+
params['event_date'] = event_date_str
|
|
112
|
+
if year is not None:
|
|
113
|
+
params['year'] = str(year)
|
|
114
|
+
if time_precision is not None:
|
|
115
|
+
params['time_precision'] = str(time_precision)
|
|
116
|
+
if disorder_type is not None:
|
|
117
|
+
params['disorder_type'] = disorder_type
|
|
118
|
+
if event_type is not None:
|
|
119
|
+
params['event_type'] = event_type
|
|
120
|
+
if sub_event_type is not None:
|
|
121
|
+
params['sub_event_type'] = sub_event_type
|
|
122
|
+
if actor1 is not None:
|
|
123
|
+
params['actor1'] = actor1
|
|
124
|
+
if assoc_actor_1 is not None:
|
|
125
|
+
params['assoc_actor_1'] = assoc_actor_1
|
|
126
|
+
if inter1 is not None:
|
|
127
|
+
params['inter1'] = str(inter1)
|
|
128
|
+
if actor2 is not None:
|
|
129
|
+
params['actor2'] = actor2
|
|
130
|
+
if assoc_actor_2 is not None:
|
|
131
|
+
params['assoc_actor_2'] = assoc_actor_2
|
|
132
|
+
if inter2 is not None:
|
|
133
|
+
params['inter2'] = str(inter2)
|
|
134
|
+
if interaction is not None:
|
|
135
|
+
params['interaction'] = str(interaction)
|
|
136
|
+
if civilian_targeting is not None:
|
|
137
|
+
params['civilian_targeting'] = civilian_targeting
|
|
138
|
+
if iso is not None:
|
|
139
|
+
params['iso'] = str(iso)
|
|
140
|
+
if region is not None:
|
|
141
|
+
params['region'] = str(region)
|
|
142
|
+
if country is not None:
|
|
143
|
+
params['country'] = country
|
|
144
|
+
if admin1 is not None:
|
|
145
|
+
params['admin1'] = admin1
|
|
146
|
+
if admin2 is not None:
|
|
147
|
+
params['admin2'] = admin2
|
|
148
|
+
if admin3 is not None:
|
|
149
|
+
params['admin3'] = admin3
|
|
150
|
+
if location is not None:
|
|
151
|
+
params['location'] = location
|
|
152
|
+
if latitude is not None:
|
|
153
|
+
params['latitude'] = str(latitude)
|
|
154
|
+
if longitude is not None:
|
|
155
|
+
params['longitude'] = str(longitude)
|
|
156
|
+
if geo_precision is not None:
|
|
157
|
+
params['geo_precision'] = str(geo_precision)
|
|
158
|
+
if source is not None:
|
|
159
|
+
params['source'] = source
|
|
160
|
+
if source_scale is not None:
|
|
161
|
+
params['source_scale'] = source_scale
|
|
162
|
+
if notes is not None:
|
|
163
|
+
params['notes'] = notes
|
|
164
|
+
if fatalities is not None:
|
|
165
|
+
params['fatalities'] = str(fatalities)
|
|
166
|
+
if timestamp is not None:
|
|
167
|
+
if isinstance(timestamp, date):
|
|
168
|
+
timestamp_str = timestamp.strftime('%Y-%m-%d')
|
|
169
|
+
else:
|
|
170
|
+
timestamp_str = str(timestamp)
|
|
171
|
+
params['timestamp'] = timestamp_str
|
|
172
|
+
if export_type is not None:
|
|
173
|
+
if isinstance(export_type, ExportType):
|
|
174
|
+
params['export_type'] = export_type.value
|
|
175
|
+
else:
|
|
176
|
+
params['export_type'] = export_type
|
|
177
|
+
|
|
178
|
+
if isinstance(page, int):
|
|
179
|
+
params['page'] = page
|
|
180
|
+
params['limit'] = limit if limit else 50
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
# Perform the API request
|
|
184
|
+
try:
|
|
185
|
+
response = self._get(self.endpoint, params=params)
|
|
186
|
+
if response.get('success'):
|
|
187
|
+
event_list = response.get('data', [])
|
|
188
|
+
return [self._parse_event(event) for event in event_list]
|
|
189
|
+
else:
|
|
190
|
+
error_message = response.get('error', [{'message': 'Unknown error'}])[0]['message']
|
|
191
|
+
raise ApiError(f"API Error: {error_message}")
|
|
192
|
+
except requests.HTTPError as e:
|
|
193
|
+
raise ApiError(f"HTTP Error: {str(e)}")
|
|
194
|
+
|
|
195
|
+
def _parse_event(self, event_data: Dict[str, Any]) -> AcledEvent:
|
|
196
|
+
"""
|
|
197
|
+
Parses raw event data into an AcledEvent TypedDict.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
event_data (Dict[str, Any]): Raw event data.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
AcledEvent: Parsed ACLED event.
|
|
204
|
+
|
|
205
|
+
Raises:
|
|
206
|
+
ValueError: If there's an error during parsing.
|
|
207
|
+
"""
|
|
208
|
+
try:
|
|
209
|
+
event_data['event_date'] = datetime.strptime(
|
|
210
|
+
event_data['event_date'], '%Y-%m-%d'
|
|
211
|
+
).date()
|
|
212
|
+
event_data['year'] = int(event_data['year'])
|
|
213
|
+
event_data['time_precision'] = int(event_data.get('time_precision', 0))
|
|
214
|
+
event_data['latitude'] = float(event_data.get('latitude', 0.0))
|
|
215
|
+
event_data['longitude'] = float(event_data.get('longitude', 0.0))
|
|
216
|
+
event_data['fatalities'] = int(event_data.get('fatalities', 0))
|
|
217
|
+
event_data['timestamp'] = datetime.fromtimestamp(
|
|
218
|
+
int(event_data['timestamp'])
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
return event_data
|
|
222
|
+
except (ValueError, KeyError) as e:
|
|
223
|
+
raise ValueError(f"Error parsing event data: {str(e)}")
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional, Union
|
|
2
|
+
import requests
|
|
3
|
+
from datetime import datetime, date
|
|
4
|
+
|
|
5
|
+
from .base_http_client import BaseHttpClient
|
|
6
|
+
from ..models.data_models import Actor
|
|
7
|
+
from ..models.enums import ExportType
|
|
8
|
+
from ..exceptions import ApiError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ActorClient(BaseHttpClient):
|
|
12
|
+
"""
|
|
13
|
+
Client for interacting with the ACLED actor endpoint.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, api_key: str, email: str):
|
|
17
|
+
super().__init__(api_key, email)
|
|
18
|
+
self.endpoint = "/actor/read"
|
|
19
|
+
|
|
20
|
+
def get_data(
|
|
21
|
+
self,
|
|
22
|
+
actor_name: Optional[str] = None,
|
|
23
|
+
first_event_date: Optional[Union[str, date]] = None,
|
|
24
|
+
last_event_date: Optional[Union[str, date]] = None,
|
|
25
|
+
event_count: Optional[int] = None,
|
|
26
|
+
export_type: Optional[Union[str, ExportType]] = ExportType.JSON,
|
|
27
|
+
limit: int = 50,
|
|
28
|
+
page: Optional[int] = None,
|
|
29
|
+
query_params: Optional[Dict[str, Any]] = None,
|
|
30
|
+
) -> List[Actor]:
|
|
31
|
+
"""
|
|
32
|
+
Retrieves Actor data based on the provided filters.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
actor_name (Optional[str]): Filter by actor name (supports LIKE).
|
|
36
|
+
first_event_date (Optional[Union[str, date]]): Filter by first event date (format 'yyyy-mm-dd').
|
|
37
|
+
last_event_date (Optional[Union[str, date]]): Filter by last event date (format 'yyyy-mm-dd').
|
|
38
|
+
event_count (Optional[int]): Filter by event count.
|
|
39
|
+
export_type (Optional[str | ExportType]): Specify the export type ('json', 'xml', 'csv', etc.).
|
|
40
|
+
limit (int): Number of records to retrieve (default is 50).
|
|
41
|
+
page (Optional[int]): Page number for pagination.
|
|
42
|
+
query_params (Optional[Dict[str, Any]]): Additional query parameters.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
List[Actor]: A list of Actors matching the filters.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
ApiError: If there's an error with the API request or response.
|
|
49
|
+
"""
|
|
50
|
+
params: Dict[str, Any] = query_params.copy() if query_params else {}
|
|
51
|
+
|
|
52
|
+
# Map arguments to query parameters, handling type conversions
|
|
53
|
+
if actor_name is not None:
|
|
54
|
+
params['actor_name'] = actor_name
|
|
55
|
+
if first_event_date is not None:
|
|
56
|
+
if isinstance(first_event_date, date):
|
|
57
|
+
params['first_event_date'] = first_event_date.strftime('%Y-%m-%d')
|
|
58
|
+
else:
|
|
59
|
+
params['first_event_date'] = first_event_date
|
|
60
|
+
if last_event_date is not None:
|
|
61
|
+
if isinstance(last_event_date, date):
|
|
62
|
+
params['last_event_date'] = last_event_date.strftime('%Y-%m-%d')
|
|
63
|
+
else:
|
|
64
|
+
params['last_event_date'] = last_event_date
|
|
65
|
+
if event_count is not None:
|
|
66
|
+
params['event_count'] = str(event_count)
|
|
67
|
+
if export_type is not None:
|
|
68
|
+
if isinstance(export_type, ExportType):
|
|
69
|
+
params['export_type'] = export_type.value
|
|
70
|
+
else:
|
|
71
|
+
params['export_type'] = export_type
|
|
72
|
+
params['limit'] = str(limit) if limit else '50'
|
|
73
|
+
if page is not None:
|
|
74
|
+
params['page'] = str(page)
|
|
75
|
+
|
|
76
|
+
# Perform the API request
|
|
77
|
+
try:
|
|
78
|
+
response = self._get(self.endpoint, params=params)
|
|
79
|
+
if response.get('success'):
|
|
80
|
+
actor_list = response.get('data', [])
|
|
81
|
+
return [self._parse_actor(actor) for actor in actor_list]
|
|
82
|
+
else:
|
|
83
|
+
error_info = response.get('error', [{'message': 'Unknown error'}])[0]
|
|
84
|
+
error_message = error_info.get('message', 'Unknown error')
|
|
85
|
+
raise ApiError(f"API Error: {error_message}")
|
|
86
|
+
except requests.HTTPError as e:
|
|
87
|
+
raise ApiError(f"HTTP Error: {str(e)}")
|
|
88
|
+
|
|
89
|
+
def _parse_actor(self, actor_data: Dict[str, Any]) -> Actor:
|
|
90
|
+
"""
|
|
91
|
+
Parses raw actor data into an Actor TypedDict.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
actor_data (Dict[str, Any]): Raw actor data.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Actor: Parsed Actor.
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
ValueError: If there's an error during parsing.
|
|
101
|
+
"""
|
|
102
|
+
try:
|
|
103
|
+
actor_data['first_event_date'] = datetime.strptime(
|
|
104
|
+
actor_data['first_event_date'], '%Y-%m-%d'
|
|
105
|
+
).date()
|
|
106
|
+
actor_data['last_event_date'] = datetime.strptime(
|
|
107
|
+
actor_data['last_event_date'], '%Y-%m-%d'
|
|
108
|
+
).date()
|
|
109
|
+
actor_data['event_count'] = int(actor_data.get('event_count', 0))
|
|
110
|
+
|
|
111
|
+
return actor_data
|
|
112
|
+
except (ValueError, KeyError) as e:
|
|
113
|
+
raise ValueError(f"Error parsing actor data: {str(e)}")
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional, Union
|
|
2
|
+
import requests
|
|
3
|
+
from datetime import datetime, date
|
|
4
|
+
|
|
5
|
+
from .base_http_client import BaseHttpClient
|
|
6
|
+
from ..models.data_models import ActorType
|
|
7
|
+
from ..models.enums import ExportType
|
|
8
|
+
from ..exceptions import ApiError
|
|
9
|
+
|
|
10
|
+
class ActorTypeClient(BaseHttpClient):
|
|
11
|
+
"""
|
|
12
|
+
Client for interacting with the ACLED actor type endpoint.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, api_key: str, email: str):
|
|
16
|
+
super().__init__(api_key, email)
|
|
17
|
+
self.endpoint = "/actortype/read"
|
|
18
|
+
|
|
19
|
+
def get_data(
|
|
20
|
+
self,
|
|
21
|
+
actor_type_id: Optional[int] = None,
|
|
22
|
+
actor_type_name: Optional[str] = None,
|
|
23
|
+
first_event_date: Optional[Union[str, date]] = None,
|
|
24
|
+
last_event_date: Optional[Union[str, date]] = None,
|
|
25
|
+
event_count: Optional[int] = None,
|
|
26
|
+
export_type: Optional[Union[str, ExportType]] = ExportType.JSON,
|
|
27
|
+
limit: int = 50,
|
|
28
|
+
page: Optional[int] = None,
|
|
29
|
+
query_params: Optional[Dict[str, Any]] = None,
|
|
30
|
+
) -> List[ActorType]:
|
|
31
|
+
"""
|
|
32
|
+
Retrieves Actor Type data based on the provided filters.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
actor_type_id (Optional[int]): Filter by actor type ID.
|
|
36
|
+
actor_type_name (Optional[str]): Filter by actor type name (supports LIKE).
|
|
37
|
+
first_event_date (Optional[Union[str, date]]): Filter by first event date (format 'yyyy-mm-dd').
|
|
38
|
+
last_event_date (Optional[Union[str, date]]): Filter by last event date (format 'yyyy-mm-dd').
|
|
39
|
+
event_count (Optional[int]): Filter by event count (default query is >=).
|
|
40
|
+
export_type (Optional[str | ExportType]): Specify the export type ('json', 'xml', 'csv', etc.).
|
|
41
|
+
limit (int): Number of records to retrieve (default is 50).
|
|
42
|
+
page (Optional[int]): Page number for pagination.
|
|
43
|
+
query_params (Optional[Dict[str, Any]]): Additional query parameters.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
List[ActorType]: A list of Actor Types matching the filters.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
ApiError: If there's an error with the API request or response.
|
|
50
|
+
"""
|
|
51
|
+
params: Dict[str, Any] = query_params.copy() if query_params else {}
|
|
52
|
+
|
|
53
|
+
# Map arguments to query parameters, handling type conversions
|
|
54
|
+
if actor_type_id is not None:
|
|
55
|
+
params['actor_type_id'] = str(actor_type_id)
|
|
56
|
+
if actor_type_name is not None:
|
|
57
|
+
params['actor_type_name'] = actor_type_name
|
|
58
|
+
if first_event_date is not None:
|
|
59
|
+
if isinstance(first_event_date, date):
|
|
60
|
+
params['first_event_date'] = first_event_date.strftime('%Y-%m-%d')
|
|
61
|
+
else:
|
|
62
|
+
params['first_event_date'] = first_event_date
|
|
63
|
+
if last_event_date is not None:
|
|
64
|
+
if isinstance(last_event_date, date):
|
|
65
|
+
params['last_event_date'] = last_event_date.strftime('%Y-%m-%d')
|
|
66
|
+
else:
|
|
67
|
+
params['last_event_date'] = last_event_date
|
|
68
|
+
if event_count is not None:
|
|
69
|
+
params['event_count'] = str(event_count)
|
|
70
|
+
if export_type is not None:
|
|
71
|
+
if isinstance(export_type, ExportType):
|
|
72
|
+
params['export_type'] = export_type.value
|
|
73
|
+
else:
|
|
74
|
+
params['export_type'] = export_type
|
|
75
|
+
params['limit'] = str(limit) if limit else '50'
|
|
76
|
+
if page is not None:
|
|
77
|
+
params['page'] = str(page)
|
|
78
|
+
|
|
79
|
+
# Perform the API request
|
|
80
|
+
try:
|
|
81
|
+
response = self._get(self.endpoint, params=params)
|
|
82
|
+
if response.get('success'):
|
|
83
|
+
actor_type_list = response.get('data', [])
|
|
84
|
+
return [self._parse_actor_type(actor_type) for actor_type in actor_type_list]
|
|
85
|
+
else:
|
|
86
|
+
error_info = response.get('error', [{'message': 'Unknown error'}])[0]
|
|
87
|
+
error_message = error_info.get('message', 'Unknown error')
|
|
88
|
+
raise ApiError(f"API Error: {error_message}")
|
|
89
|
+
except requests.HTTPError as e:
|
|
90
|
+
raise ApiError(f"HTTP Error: {str(e)}")
|
|
91
|
+
|
|
92
|
+
def _parse_actor_type(self, actor_type_data: Dict[str, Any]) -> ActorType:
|
|
93
|
+
"""
|
|
94
|
+
Parses raw actor type data into an ActorType TypedDict.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
actor_type_data (Dict[str, Any]): Raw actor type data.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
ActorType: Parsed ActorType.
|
|
101
|
+
|
|
102
|
+
Raises:
|
|
103
|
+
ValueError: If there's an error during parsing.
|
|
104
|
+
"""
|
|
105
|
+
try:
|
|
106
|
+
actor_type_data['actor_type_id'] = int(actor_type_data.get('actor_type_id', 0))
|
|
107
|
+
actor_type_data['first_event_date'] = datetime.strptime(
|
|
108
|
+
actor_type_data['first_event_date'], '%Y-%m-%d'
|
|
109
|
+
).date()
|
|
110
|
+
actor_type_data['last_event_date'] = datetime.strptime(
|
|
111
|
+
actor_type_data['last_event_date'], '%Y-%m-%d'
|
|
112
|
+
).date()
|
|
113
|
+
actor_type_data['event_count'] = int(actor_type_data.get('event_count', 0))
|
|
114
|
+
|
|
115
|
+
return actor_type_data # This will be of type ActorType
|
|
116
|
+
except (ValueError, KeyError) as e:
|
|
117
|
+
raise ValueError(f"Error parsing actor type data: {str(e)}")
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
from os import environ
|
|
4
|
+
from ..exceptions import AcledMissingAuthError
|
|
5
|
+
from ..log import AcledLogger
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseHttpClient(object):
|
|
9
|
+
"""
|
|
10
|
+
A base HTTP client that provides basic GET and POST request functionality.
|
|
11
|
+
"""
|
|
12
|
+
BASE_URL = environ.get("ACLED_API_HOST", "https://api.acleddata.com")
|
|
13
|
+
|
|
14
|
+
def __init__(self, api_key: Optional[str] = None, email: Optional[str] = None):
|
|
15
|
+
self.api_key = api_key if api_key else environ.get("ACLED_API_KEY")
|
|
16
|
+
if not self.api_key:
|
|
17
|
+
raise AcledMissingAuthError("API key is required")
|
|
18
|
+
self.email = email if email else environ.get("ACLED_EMAIL")
|
|
19
|
+
if not self.email:
|
|
20
|
+
raise AcledMissingAuthError("Email is required")
|
|
21
|
+
self.session = requests.Session()
|
|
22
|
+
self.session.headers.update({'Content-Type': 'application/json'})
|
|
23
|
+
self.log = AcledLogger().get_logger()
|
|
24
|
+
|
|
25
|
+
def _get(
|
|
26
|
+
self, endpoint: str, params: Optional[Dict[str, Any]] = None
|
|
27
|
+
) -> Dict[str, Any]:
|
|
28
|
+
if params is None:
|
|
29
|
+
params = {}
|
|
30
|
+
# Include API key and email in all requests
|
|
31
|
+
params['key'] = self.api_key
|
|
32
|
+
params['email'] = self.email
|
|
33
|
+
url = f"{self.BASE_URL}{endpoint}"
|
|
34
|
+
|
|
35
|
+
self.log.debug(f"Constructed URL: {url}")
|
|
36
|
+
self.log.debug(f"Query Parameters: {params}")
|
|
37
|
+
|
|
38
|
+
response = self.session.get(url, params=params)
|
|
39
|
+
response.raise_for_status()
|
|
40
|
+
self.log.debug(f"Response content:\n{response.content}")
|
|
41
|
+
return response.json()
|
|
42
|
+
|
|
43
|
+
def _post(
|
|
44
|
+
self, endpoint: str, data: Optional[Dict[str, Any]] = None
|
|
45
|
+
) -> Dict[str, Any]:
|
|
46
|
+
if data is None:
|
|
47
|
+
data = {}
|
|
48
|
+
# Include API key and email in all requests
|
|
49
|
+
data['key'] = self.api_key
|
|
50
|
+
data['email'] = self.email
|
|
51
|
+
url = f"{self.BASE_URL}{endpoint}"
|
|
52
|
+
response = self.session.post(url, json=data)
|
|
53
|
+
response.raise_for_status()
|
|
54
|
+
return response.json()
|
acled/clients/client.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from .acled_data_client import AcledDataClient
|
|
4
|
+
from .actor_client import ActorClient
|
|
5
|
+
from .actor_type_client import ActorTypeClient
|
|
6
|
+
from .country_client import CountryClient
|
|
7
|
+
from .region_client import RegionClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AcledClient:
|
|
11
|
+
"""
|
|
12
|
+
Main ACLED client that provides access to different API endpoints.
|
|
13
|
+
|
|
14
|
+
This client aggregates several sub-clients to provide a relatively complete interface for
|
|
15
|
+
interacting with the ACLED API. Each sub-client is responsible for a specific endpoint,
|
|
16
|
+
making it easier to organize and manage the API interactions while still providing a
|
|
17
|
+
single point of entry.
|
|
18
|
+
|
|
19
|
+
Methods:
|
|
20
|
+
get_data:
|
|
21
|
+
Returns:
|
|
22
|
+
Function to fetch the ACLED data.
|
|
23
|
+
|
|
24
|
+
get_actor_data:
|
|
25
|
+
Returns:
|
|
26
|
+
Function to fetch the actor data.
|
|
27
|
+
|
|
28
|
+
get_actor_type_data:
|
|
29
|
+
Returns:
|
|
30
|
+
Function to fetch the actor type data.
|
|
31
|
+
|
|
32
|
+
get_country_data:
|
|
33
|
+
Returns:
|
|
34
|
+
Function to fetch country data.
|
|
35
|
+
|
|
36
|
+
get_region_data:
|
|
37
|
+
Returns:
|
|
38
|
+
Function to fetch region data.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
api_key: Optional[str] = None,
|
|
44
|
+
email: Optional[str] = None
|
|
45
|
+
):
|
|
46
|
+
self._acled_data_client = AcledDataClient(api_key, email)
|
|
47
|
+
self._actor_client = ActorClient(api_key, email)
|
|
48
|
+
self._country_client = CountryClient(api_key, email)
|
|
49
|
+
self._region_client = RegionClient(api_key, email)
|
|
50
|
+
self._actor_type_client = ActorTypeClient(api_key, email)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def get_data(self):
|
|
55
|
+
return self._acled_data_client.get_data
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def get_actor_data(self):
|
|
59
|
+
return self._actor_client.get_data
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def get_actor_type_data(self):
|
|
63
|
+
return self._actor_type_client.get_data
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def get_country_data(self):
|
|
67
|
+
return self._country_client.get_data
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def get_region_data(self):
|
|
71
|
+
return self._region_client.get_data
|