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 ADDED
@@ -0,0 +1 @@
1
+ from .clients import AcledClient
@@ -0,0 +1,3 @@
1
+ from .client import AcledClient
2
+ from .acled_data_client import AcledDataClient
3
+ from .base_http_client import BaseHttpClient
@@ -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()
@@ -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