ebird-api-data 0.3.7__tar.gz → 0.4.0__tar.gz

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.
Files changed (39) hide show
  1. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/PKG-INFO +3 -4
  2. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/README.md +1 -1
  3. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/pyproject.toml +3 -4
  4. ebird_api_data-0.4.0/src/ebird/api/data/__init__.py +1 -0
  5. ebird_api_data-0.4.0/src/ebird/api/data/factories.py +432 -0
  6. ebird_api_data-0.4.0/src/ebird/api/data/faker_providers.py +345 -0
  7. ebird_api_data-0.4.0/src/ebird/api/data/fixtures/README.md +355 -0
  8. ebird_api_data-0.4.0/src/ebird/api/data/fixtures/generate_portugal_data.py +530 -0
  9. ebird_api_data-0.4.0/src/ebird/api/data/fixtures/portugal_observations.json +144813 -0
  10. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/loaders.py +86 -40
  11. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/management/commands/add_checklists.py +36 -21
  12. ebird_api_data-0.4.0/src/ebird/api/data/migrations/0002_expand_filter.py +29 -0
  13. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/models/filter.py +38 -1
  14. ebird_api_data-0.4.0/src/ebird/api/data/models/models.md +242 -0
  15. ebird_api_data-0.3.7/src/ebird/api/data/__init__.py +0 -1
  16. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/.gitignore +0 -0
  17. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/LICENSE +0 -0
  18. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/admin.py +0 -0
  19. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/apps.py +0 -0
  20. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/fields.py +0 -0
  21. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/locale/en/LC_MESSAGES/django.mo +0 -0
  22. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/locale/en/LC_MESSAGES/django.po +0 -0
  23. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/management/__init__.py +0 -0
  24. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/management/commands/__init__.py +0 -0
  25. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/migrations/0001_initial.py +0 -0
  26. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/migrations/__init__.py +0 -0
  27. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/models/__init__.py +0 -0
  28. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/models/checklist.py +0 -0
  29. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/models/country.py +0 -0
  30. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/models/county.py +0 -0
  31. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/models/location.py +0 -0
  32. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/models/observation.py +0 -0
  33. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/models/observer.py +0 -0
  34. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/models/species.py +0 -0
  35. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/models/state.py +0 -0
  36. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/templates/admin/data/species/change_list_object_tools.html +0 -0
  37. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/templates/admin/data/species/fetch_species.html +0 -0
  38. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/templates/data/widgets/translation_field.html +0 -0
  39. {ebird_api_data-0.3.7 → ebird_api_data-0.4.0}/src/ebird/api/data/widgets.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ebird-api-data
3
- Version: 0.3.7
3
+ Version: 0.4.0
4
4
  Summary: eBird API Data is a reusable Django app for loading data from eBird into a database.
5
5
  Author-email: Stuart MacKay <smackay@flagstonesoftware.com>
6
6
  License-Expression: MIT
@@ -23,8 +23,7 @@ Classifier: Topic :: Scientific/Engineering :: Information Analysis
23
23
  Requires-Python: >=3.10
24
24
  Requires-Dist: beautifulsoup4>=4.13.3
25
25
  Requires-Dist: django>=4.2
26
- Requires-Dist: ebird-api-requests>=4.0.1
27
- Requires-Dist: ebird-codes>=0.0.2
26
+ Requires-Dist: ebird-api-requests==4.0.2
28
27
  Requires-Dist: lxml>=6.0.0
29
28
  Description-Content-Type: text/markdown
30
29
 
@@ -142,7 +141,7 @@ EBIRD_API_KEY=<my api key>
142
141
  Now, download data from the API:
143
142
 
144
143
  ```shell
145
- python manage.py add_checklists --days 2 US-NY-109
144
+ python manage.py add_checklists --days -2 US-NY-109
146
145
  ```
147
146
 
148
147
  This loads all the checklists, submitted in the past two days by birders in
@@ -112,7 +112,7 @@ EBIRD_API_KEY=<my api key>
112
112
  Now, download data from the API:
113
113
 
114
114
  ```shell
115
- python manage.py add_checklists --days 2 US-NY-109
115
+ python manage.py add_checklists --days -2 US-NY-109
116
116
  ```
117
117
 
118
118
  This loads all the checklists, submitted in the past two days by birders in
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ebird-api-data"
3
- version = "0.3.7"
3
+ version = "0.4.0"
4
4
  description = "eBird API Data is a reusable Django app for loading data from eBird into a database."
5
5
  keywords = ["checklists", "observations", "species", "Django", "eBird", "models"]
6
6
  license = "MIT"
@@ -13,8 +13,7 @@ requires-python = ">=3.10"
13
13
  dependencies = [
14
14
  "beautifulsoup4>=4.13.3",
15
15
  "django>=4.2",
16
- "ebird-api-requests>=4.0.1",
17
- "ebird-codes>=0.0.2",
16
+ "ebird-api-requests==4.0.2",
18
17
  "lxml>=6.0.0",
19
18
  ]
20
19
  classifiers = [
@@ -65,7 +64,7 @@ dev-dependencies = [
65
64
  ]
66
65
 
67
66
  [tool.bumpversion]
68
- current_version = "0.3.7"
67
+ current_version = "0.4.0"
69
68
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
70
69
  serialize = ["{major}.{minor}.{patch}"]
71
70
  ignore_missing_version = false
@@ -0,0 +1 @@
1
+ __version__ = "0.4.0"
@@ -0,0 +1,432 @@
1
+ """
2
+ FactoryBoy factories for eBird API data models.
3
+
4
+ These factories provide an easy way to create test data with realistic,
5
+ contextually appropriate values using Faker providers.
6
+
7
+ Usage:
8
+ from ebird.api.data.factories import ChecklistFactory, ObservationFactory
9
+
10
+ # Create a single checklist with observations
11
+ checklist = ChecklistFactory()
12
+
13
+ # Create multiple checklists
14
+ checklists = ChecklistFactory.create_batch(10)
15
+
16
+ # Create checklist with specific location
17
+ checklist = ChecklistFactory(location__name="Ria de Aveiro")
18
+
19
+ # Create observation with specific species
20
+ observation = ObservationFactory(species__species_code="houspa")
21
+ """
22
+
23
+ import factory
24
+ from factory import fuzzy
25
+ from factory.django import DjangoModelFactory
26
+ from faker import Faker
27
+
28
+ from ebird.api.data.models import (
29
+ Country,
30
+ State,
31
+ County,
32
+ Location,
33
+ Species,
34
+ Observer,
35
+ Checklist,
36
+ Observation,
37
+ Filter,
38
+ )
39
+ from ebird.api.data.faker_providers import (
40
+ BirdProvider,
41
+ LocationProvider,
42
+ ObserverProvider,
43
+ )
44
+
45
+ # Initialize Faker with custom providers
46
+ fake = Faker()
47
+ fake.add_provider(BirdProvider)
48
+ fake.add_provider(LocationProvider)
49
+ fake.add_provider(ObserverProvider)
50
+
51
+
52
+ class CountryFactory(DjangoModelFactory):
53
+ """Factory for Country model."""
54
+
55
+ class Meta:
56
+ model = Country
57
+ django_get_or_create = ("code",)
58
+
59
+ code = "PT"
60
+ name = "Portugal"
61
+ place = "PT"
62
+
63
+
64
+ class StateFactory(DjangoModelFactory):
65
+ """Factory for State model."""
66
+
67
+ class Meta:
68
+ model = State
69
+ django_get_or_create = ("code",)
70
+
71
+ code = factory.LazyFunction(lambda: fake.portuguese_state_code())
72
+ name = factory.LazyAttribute(lambda obj: fake.portuguese_state_name())
73
+ place = factory.LazyAttribute(lambda obj: f"PT, {obj.name}")
74
+
75
+
76
+ class CountyFactory(DjangoModelFactory):
77
+ """Factory for County model."""
78
+
79
+ class Meta:
80
+ model = County
81
+
82
+ code = factory.Sequence(lambda n: f"PT-{n:02d}-CTY")
83
+ name = factory.Faker("city", locale="pt_PT")
84
+ place = factory.LazyAttribute(lambda obj: f"PT, {obj.name}")
85
+
86
+
87
+ class LocationFactory(DjangoModelFactory):
88
+ """Factory for Location model."""
89
+
90
+ class Meta:
91
+ model = Location
92
+
93
+ identifier = factory.Sequence(lambda n: f"L{n:06d}")
94
+ original = factory.LazyFunction(lambda: fake.location_name())
95
+ name = factory.LazyAttribute(lambda obj: obj.original)
96
+
97
+ country = factory.SubFactory(CountryFactory)
98
+ state = factory.SubFactory(StateFactory)
99
+ county = factory.SubFactory(CountyFactory)
100
+
101
+ latitude = factory.LazyFunction(lambda: fake.portugal_coordinates()[0])
102
+ longitude = factory.LazyFunction(lambda: fake.portugal_coordinates()[1])
103
+
104
+ url = factory.LazyAttribute(
105
+ lambda obj: f"https://ebird.org/hotspot/{obj.identifier}" if obj.hotspot else ""
106
+ )
107
+ hotspot = factory.LazyFunction(lambda: fake.location_is_hotspot())
108
+
109
+ data = factory.LazyFunction(lambda: {"habitat": fake.bird_habitat()})
110
+
111
+
112
+ class SpeciesFactory(DjangoModelFactory):
113
+ """Factory for Species model."""
114
+
115
+ class Meta:
116
+ model = Species
117
+ django_get_or_create = ("species_code",)
118
+
119
+ species_code = factory.LazyFunction(lambda: fake.bird_species_code())
120
+ taxon_order = factory.Faker("random_int", min=1, max=5000)
121
+ order = "Passeriformes"
122
+ category = "species"
123
+ family_code = factory.Faker("word")
124
+
125
+ common_name = factory.LazyAttribute(
126
+ lambda obj: f'{{"en": "{fake.bird_species_name(obj.species_code)}"}}'
127
+ )
128
+ scientific_name = factory.Faker("sentence", nb_words=2)
129
+
130
+ family_common_name = factory.LazyAttribute(
131
+ lambda obj: f'{{"en": "{obj.family_code.title()}"}}'
132
+ )
133
+ family_scientific_name = ""
134
+
135
+ data = factory.Dict({"habitats": factory.List([factory.LazyFunction(lambda: fake.bird_habitat())])})
136
+
137
+
138
+ class ObserverFactory(DjangoModelFactory):
139
+ """Factory for Observer model."""
140
+
141
+ class Meta:
142
+ model = Observer
143
+
144
+ identifier = factory.LazyFunction(lambda: fake.observer_identifier())
145
+ name = factory.LazyFunction(lambda: fake.observer_name())
146
+ original = factory.LazyAttribute(lambda obj: obj.name)
147
+ multiple = False
148
+ enabled = True
149
+
150
+ data = factory.LazyFunction(
151
+ lambda: {"experience": fake.observer_experience_level()}
152
+ )
153
+
154
+
155
+ class ChecklistFactory(DjangoModelFactory):
156
+ """
157
+ Factory for Checklist model.
158
+
159
+ Creates realistic checklists with appropriate effort metrics based on protocol.
160
+ """
161
+
162
+ class Meta:
163
+ model = Checklist
164
+
165
+ identifier = factory.Sequence(lambda n: f"S{n:08d}")
166
+
167
+ # Timestamps
168
+ added = factory.Faker("date_time_this_year", tzinfo=None)
169
+ edited = factory.LazyAttribute(lambda obj: obj.added)
170
+ published = True
171
+
172
+ # Relationships
173
+ country = factory.SubFactory(CountryFactory)
174
+ state = factory.SubFactory(StateFactory)
175
+ county = factory.SubFactory(CountyFactory)
176
+ location = factory.SubFactory(LocationFactory)
177
+ observer = factory.SubFactory(ObserverFactory)
178
+
179
+ # Counts (will be set by post_generation for observations)
180
+ observer_count = 1
181
+ species_count = factory.Faker("random_int", min=3, max=20)
182
+
183
+ # Date/Time
184
+ date = factory.LazyAttribute(lambda obj: obj.started.date() if obj.started else fake.date_this_year())
185
+ time = factory.LazyAttribute(lambda obj: obj.started.time() if obj.started else fake.time())
186
+ started = factory.Faker("date_time_this_year", tzinfo=None)
187
+
188
+ # Protocol and effort
189
+ protocol_code = factory.LazyFunction(lambda: fake.checklist_protocol_code())
190
+ project_code = "EBIRD"
191
+
192
+ duration = factory.LazyAttribute(
193
+ lambda obj: fake.checklist_duration(obj.protocol_code)
194
+ )
195
+ distance = factory.LazyAttribute(
196
+ lambda obj: fake.checklist_distance(obj.protocol_code)
197
+ )
198
+ area = None
199
+
200
+ complete = True
201
+ comments = ""
202
+
203
+ url = factory.LazyAttribute(
204
+ lambda obj: f"https://ebird.org/checklist/{obj.identifier}"
205
+ )
206
+
207
+ data = factory.Dict({})
208
+
209
+ @factory.post_generation
210
+ def with_observations(self, create, extracted, **kwargs):
211
+ """
212
+ Post-generation hook to create observations for the checklist.
213
+
214
+ Usage:
215
+ ChecklistFactory(with_observations=5) # Creates 5 observations
216
+ ChecklistFactory(with_observations=True) # Creates 3-10 observations
217
+ """
218
+ if not create:
219
+ return
220
+
221
+ if extracted:
222
+ # If boolean True, create random number of observations
223
+ if extracted is True:
224
+ num_observations = fake.random_int(min=3, max=10)
225
+ else:
226
+ num_observations = extracted
227
+
228
+ # Get habitat from location
229
+ habitat = self.location.data.get("habitat", "urban")
230
+
231
+ # Get appropriate species for habitat
232
+ species_codes = fake.bird_species_list_for_habitat(
233
+ habitat, min_species=num_observations, max_species=num_observations
234
+ )
235
+
236
+ for species_code in species_codes:
237
+ species, _ = Species.objects.get_or_create(
238
+ species_code=species_code,
239
+ defaults={
240
+ "common_name": f'{{"en": "{fake.bird_species_name(species_code)}"}}',
241
+ "scientific_name": f"Species {species_code}",
242
+ "taxon_order": fake.random_int(min=1, max=5000),
243
+ "order": "Passeriformes",
244
+ "category": "species",
245
+ "family_code": "family1",
246
+ }
247
+ )
248
+
249
+ ObservationFactory(
250
+ checklist=self,
251
+ species=species,
252
+ observer=self.observer,
253
+ country=self.country,
254
+ state=self.state,
255
+ county=self.county,
256
+ location=self.location,
257
+ date=self.date,
258
+ time=self.time,
259
+ started=self.started,
260
+ )
261
+
262
+ # Update species count
263
+ self.species_count = num_observations
264
+ self.save()
265
+
266
+
267
+ class ObservationFactory(DjangoModelFactory):
268
+ """
269
+ Factory for Observation model.
270
+
271
+ Creates realistic observations with appropriate counts and media flags.
272
+ """
273
+
274
+ class Meta:
275
+ model = Observation
276
+
277
+ identifier = factory.Sequence(lambda n: f"OBS{n:09d}")
278
+
279
+ # Timestamps
280
+ edited = factory.Faker("date_time_this_year", tzinfo=None)
281
+ published = True
282
+
283
+ # Relationships
284
+ checklist = factory.SubFactory(ChecklistFactory)
285
+ species = factory.SubFactory(SpeciesFactory)
286
+ observer = factory.SubFactory(ObserverFactory)
287
+
288
+ country = factory.LazyAttribute(lambda obj: obj.checklist.country)
289
+ state = factory.LazyAttribute(lambda obj: obj.checklist.state)
290
+ county = factory.LazyAttribute(lambda obj: obj.checklist.county)
291
+ location = factory.LazyAttribute(lambda obj: obj.checklist.location)
292
+
293
+ # Date/Time (inherit from checklist)
294
+ date = factory.LazyAttribute(lambda obj: obj.checklist.date)
295
+ time = factory.LazyAttribute(lambda obj: obj.checklist.time)
296
+ started = factory.LazyAttribute(lambda obj: obj.checklist.started)
297
+
298
+ # Count based on species
299
+ count = factory.LazyAttribute(
300
+ lambda obj: fake.bird_count(obj.species.species_code)
301
+ )
302
+
303
+ # Media flags (realistic probabilities)
304
+ audio = factory.LazyFunction(lambda: fake.observation_has_media()["audio"])
305
+ photo = factory.LazyFunction(lambda: fake.observation_has_media()["photo"])
306
+ video = factory.LazyFunction(lambda: fake.observation_has_media()["video"])
307
+
308
+ approved = True
309
+ reason = ""
310
+ comments = ""
311
+
312
+ urn = factory.LazyAttribute(lambda obj: f"urn:observation:{obj.identifier}")
313
+
314
+ data = factory.Dict({})
315
+
316
+
317
+ class FilterFactory(DjangoModelFactory):
318
+ """
319
+ Factory for Filter model.
320
+
321
+ Creates filters for bulk-updating species in observations.
322
+ """
323
+
324
+ class Meta:
325
+ model = Filter
326
+
327
+ enabled = True
328
+ name = factory.Faker("sentence", nb_words=3)
329
+
330
+ species = factory.SubFactory(SpeciesFactory)
331
+ update_species = factory.SubFactory(SpeciesFactory)
332
+
333
+ # Optional geographic filters
334
+ country = factory.Maybe(
335
+ "with_country",
336
+ yes_declaration=factory.SubFactory(CountryFactory),
337
+ no_declaration=None
338
+ )
339
+ state = factory.Maybe(
340
+ "with_state",
341
+ yes_declaration=factory.SubFactory(StateFactory),
342
+ no_declaration=None
343
+ )
344
+ county = factory.Maybe(
345
+ "with_county",
346
+ yes_declaration=factory.SubFactory(CountyFactory),
347
+ no_declaration=None
348
+ )
349
+ location = factory.Maybe(
350
+ "with_location",
351
+ yes_declaration=factory.SubFactory(LocationFactory),
352
+ no_declaration=None
353
+ )
354
+
355
+ class Params:
356
+ # Parameters to control which geographic filters to include
357
+ with_country = False
358
+ with_state = False
359
+ with_county = False
360
+ with_location = False
361
+
362
+
363
+ # Convenience functions for common scenarios
364
+
365
+ def create_coastal_checklist(**kwargs):
366
+ """Create a checklist at a coastal location with appropriate species."""
367
+ location = LocationFactory(data={"habitat": "coastal"})
368
+ checklist = ChecklistFactory(
369
+ location=location,
370
+ with_observations=True,
371
+ **kwargs
372
+ )
373
+ return checklist
374
+
375
+
376
+ def create_wetland_checklist(**kwargs):
377
+ """Create a checklist at a wetland location with appropriate species."""
378
+ location = LocationFactory(data={"habitat": "coastal_wetland"})
379
+ checklist = ChecklistFactory(
380
+ location=location,
381
+ with_observations=True,
382
+ **kwargs
383
+ )
384
+ return checklist
385
+
386
+
387
+ def create_urban_checklist(**kwargs):
388
+ """Create a checklist at an urban location with appropriate species."""
389
+ location = LocationFactory(data={"habitat": "urban"})
390
+ checklist = ChecklistFactory(
391
+ location=location,
392
+ with_observations=True,
393
+ **kwargs
394
+ )
395
+ return checklist
396
+
397
+
398
+ def create_grassland_checklist(**kwargs):
399
+ """Create a checklist at a grassland location with steppe specialists."""
400
+ location = LocationFactory(data={"habitat": "grassland"})
401
+ checklist = ChecklistFactory(
402
+ location=location,
403
+ with_observations=True,
404
+ **kwargs
405
+ )
406
+ return checklist
407
+
408
+
409
+ def create_expert_observer_checklist(**kwargs):
410
+ """Create a checklist by an expert observer with many species."""
411
+ observer = ObserverFactory(data={"experience": "expert"})
412
+ checklist = ChecklistFactory(
413
+ observer=observer,
414
+ duration=fuzzy.FuzzyInteger(120, 300).fuzz(),
415
+ species_count=fuzzy.FuzzyInteger(15, 30).fuzz(),
416
+ with_observations=fuzzy.FuzzyInteger(15, 30).fuzz(),
417
+ **kwargs
418
+ )
419
+ return checklist
420
+
421
+
422
+ def create_beginner_observer_checklist(**kwargs):
423
+ """Create a checklist by a beginner observer with fewer species."""
424
+ observer = ObserverFactory(data={"experience": "beginner"})
425
+ checklist = ChecklistFactory(
426
+ observer=observer,
427
+ duration=fuzzy.FuzzyInteger(30, 90).fuzz(),
428
+ species_count=fuzzy.FuzzyInteger(3, 8).fuzz(),
429
+ with_observations=fuzzy.FuzzyInteger(3, 8).fuzz(),
430
+ **kwargs
431
+ )
432
+ return checklist