udata-hydra 2.0.0.dev3146__tar.gz → 2.0.0.dev3158__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 (54) hide show
  1. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/PKG-INFO +1 -1
  2. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/pyproject.toml +1 -1
  3. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/cli.py +1 -3
  4. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/crawl.py +15 -30
  5. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/db/check.py +4 -4
  6. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/routes/__init__.py +1 -2
  7. udata_hydra-2.0.0.dev3158/udata_hydra/routes/checks.py +26 -0
  8. udata_hydra-2.0.0.dev3146/udata_hydra/routes/checks.py +0 -68
  9. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/README.md +0 -0
  10. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/__init__.py +0 -0
  11. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/analysis/__init__.py +0 -0
  12. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/analysis/csv.py +0 -0
  13. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/analysis/errors.py +0 -0
  14. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/analysis/helpers.py +0 -0
  15. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/analysis/resource.py +0 -0
  16. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/app.py +0 -0
  17. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/config_default.toml +0 -0
  18. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/context.py +0 -0
  19. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/db/__init__.py +0 -0
  20. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/db/resource.py +0 -0
  21. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/logger.py +0 -0
  22. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/migrations/__init__.py +0 -0
  23. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/migrations/csv/20221205_initial_up_rev1.sql +0 -0
  24. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/migrations/csv/20230130_drop_migrations.sql +0 -0
  25. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/migrations/csv/20230206_datetime_aware.sql +0 -0
  26. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/migrations/main/20221205_initial_up_rev1.sql +0 -0
  27. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/migrations/main/20221206_rev1_up_rev2.sql +0 -0
  28. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/migrations/main/20221206_rev2_up_rev3.sql +0 -0
  29. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/migrations/main/20221208_rev3_up_rev4.sql +0 -0
  30. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/migrations/main/20221208_rev4_up_rev5.sql +0 -0
  31. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/migrations/main/20230119_rev5_up_rev6.sql +0 -0
  32. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/migrations/main/20230121_rev6_up_rev7.sql +0 -0
  33. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/migrations/main/20230121_rev7_up_rev8.sql +0 -0
  34. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/migrations/main/20230130_drop_migrations.sql +0 -0
  35. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/migrations/main/20230206_datetime_aware.sql +0 -0
  36. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/migrations/main/20230515_rev8_up_rev9.sql +0 -0
  37. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/migrations/main/20230606_rev9_up_rev10.sql +0 -0
  38. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/migrations/main/20231102_drop_csv_analysis.sql +0 -0
  39. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/routes/resources.py +0 -0
  40. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/routes/status.py +0 -0
  41. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/schemas/__init__.py +0 -0
  42. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/schemas/check.py +0 -0
  43. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/schemas/resource.py +0 -0
  44. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/utils/__init__.py +0 -0
  45. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/utils/auth.py +0 -0
  46. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/utils/csv.py +0 -0
  47. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/utils/file.py +0 -0
  48. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/utils/http.py +0 -0
  49. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/utils/minio.py +0 -0
  50. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/utils/parquet.py +0 -0
  51. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/utils/queue.py +0 -0
  52. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/utils/reader.py +0 -0
  53. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/utils/timer.py +0 -0
  54. {udata_hydra-2.0.0.dev3146 → udata_hydra-2.0.0.dev3158}/udata_hydra/worker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: udata-hydra
3
- Version: 2.0.0.dev3146
3
+ Version: 2.0.0.dev3158
4
4
  Summary: Async crawler and parsing service for data.gouv.fr
5
5
  License: MIT
6
6
  Author: Opendata Team
@@ -9,7 +9,7 @@ readme = "README.md"
9
9
 
10
10
  [tool.poetry]
11
11
  name = "udata-hydra"
12
- version = "2.0.0.dev3146"
12
+ version = "2.0.0.dev3158"
13
13
  description = "Async crawler and parsing service for data.gouv.fr"
14
14
  authors = ["Opendata Team <opendatateam@data.gouv.fr>"]
15
15
  license = "MIT"
@@ -144,9 +144,7 @@ async def check_resource(resource_id: str, method: str = "get"):
144
144
  log.error("Resource not found in catalog")
145
145
  return
146
146
  async with aiohttp.ClientSession(timeout=None) as session:
147
- await crawl_check_url(
148
- url=res[0], resource_id=None, session=session, method=method, worker_priority="high"
149
- )
147
+ await crawl_check_url(url=res[0], resource_id=None, session=session, method=method)
150
148
 
151
149
 
152
150
  @cli(name="analyse-csv")
@@ -18,12 +18,10 @@ from udata_hydra.utils import queue, send
18
18
 
19
19
  results: defaultdict = defaultdict(int)
20
20
 
21
- RESOURCE_RESPONSE_STATUSES = {
22
- "OK": "ok",
23
- "TIMEOUT": "timeout",
24
- "ERROR": "error",
25
- "BACKOFF": "backoff",
26
- }
21
+ STATUS_OK = "ok"
22
+ STATUS_TIMEOUT = "timeout"
23
+ STATUS_ERROR = "error"
24
+ STATUS_BACKOFF = "backoff"
27
25
 
28
26
  log = setup_logging()
29
27
 
@@ -237,12 +235,7 @@ def has_nice_head(resp) -> bool:
237
235
 
238
236
 
239
237
  async def check_url(
240
- url: str,
241
- resource_id: str,
242
- session,
243
- sleep: float = 0,
244
- method: str = "head",
245
- worker_priority: str = "default",
238
+ url: str, resource_id: str, session, sleep: float = 0, method: str = "head"
246
239
  ) -> str:
247
240
  log.debug(f"check {url}, sleep {sleep}, method {method}")
248
241
 
@@ -261,14 +254,14 @@ async def check_url(
261
254
  "timeout": False,
262
255
  }
263
256
  )
264
- return RESOURCE_RESPONSE_STATUSES["ERROR"]
257
+ return STATUS_ERROR
265
258
 
266
259
  should_backoff, reason = await is_backoff(domain)
267
260
  if should_backoff:
268
261
  log.info(f"backoff {domain} ({reason})")
269
262
  # skip this URL, it will come back in a next batch
270
263
  await Resource.update(resource_id=resource_id, data={"status": None, "priority": False})
271
- return RESOURCE_RESPONSE_STATUSES["BACKOFF"]
264
+ return STATUS_BACKOFF
272
265
 
273
266
  try:
274
267
  start = time.time()
@@ -277,9 +270,7 @@ async def check_url(
277
270
  async with _method(url, timeout=timeout, allow_redirects=True) as resp:
278
271
  end = time.time()
279
272
  if method != "get" and not has_nice_head(resp):
280
- return await check_url(
281
- url, resource_id, session, method="get", worker_priority=worker_priority
282
- )
273
+ return await check_url(url, resource_id, session, method="get")
283
274
  resp.raise_for_status()
284
275
 
285
276
  check_id, is_first_check = await process_check_data(
@@ -291,12 +282,13 @@ async def check_url(
291
282
  "headers": convert_headers(resp.headers),
292
283
  "timeout": False,
293
284
  "response_time": end - start,
294
- },
285
+ }
295
286
  )
296
287
 
297
- queue.enqueue(process_resource, check_id, is_first_check, _priority=worker_priority)
288
+ # Analyse the resource
289
+ queue.enqueue(process_resource, check_id, is_first_check, _priority="low")
298
290
 
299
- return RESOURCE_RESPONSE_STATUSES["OK"]
291
+ return STATUS_OK
300
292
  except asyncio.exceptions.TimeoutError:
301
293
  await process_check_data(
302
294
  {
@@ -306,7 +298,7 @@ async def check_url(
306
298
  "timeout": True,
307
299
  }
308
300
  )
309
- return RESOURCE_RESPONSE_STATUSES["TIMEOUT"]
301
+ return STATUS_TIMEOUT
310
302
  # TODO: debug AssertionError, should be caught in DB now
311
303
  # File "[...]aiohttp/connector.py", line 991, in _create_direct_connection
312
304
  # assert port is not None
@@ -330,7 +322,7 @@ async def check_url(
330
322
  }
331
323
  )
332
324
  log.warning(f"Crawling error for url {url}", exc_info=e)
333
- return RESOURCE_RESPONSE_STATUSES["ERROR"]
325
+ return STATUS_ERROR
334
326
 
335
327
 
336
328
  async def crawl_urls(to_parse: list[str]) -> None:
@@ -340,14 +332,7 @@ async def crawl_urls(to_parse: list[str]) -> None:
340
332
  timeout=None, headers={"user-agent": config.USER_AGENT}
341
333
  ) as session:
342
334
  for row in to_parse:
343
- tasks.append(
344
- check_url(
345
- url=row["url"],
346
- resource_id=row["resource_id"],
347
- session=session,
348
- worker_priority="low",
349
- )
350
- )
335
+ tasks.append(check_url(url=row["url"], resource_id=row["resource_id"], session=session))
351
336
  for task in asyncio.as_completed(tasks):
352
337
  result = await task
353
338
  results[result] += 1
@@ -62,12 +62,12 @@ class Check:
62
62
  This use the info from the last check of the same resource
63
63
  """
64
64
  data = convert_dict_values_to_json(data)
65
- q1: str = compute_insert_query(table_name="checks", data=data)
65
+ q = compute_insert_query(table_name="checks", data=data)
66
66
  pool = await context.pool()
67
67
  async with pool.acquire() as connection:
68
- last_check = await connection.fetchrow(q1, *data.values())
69
- q2 = """UPDATE catalog SET last_check = $1 WHERE resource_id = $2"""
70
- await connection.execute(q2, last_check["id"], data["resource_id"])
68
+ last_check = await connection.fetchrow(q, *data.values())
69
+ q = """UPDATE catalog SET last_check = $1 WHERE resource_id = $2"""
70
+ await connection.execute(q, last_check["id"], data["resource_id"])
71
71
  return last_check["id"]
72
72
 
73
73
  @classmethod
@@ -1,6 +1,6 @@
1
1
  from aiohttp import web
2
2
 
3
- from udata_hydra.routes.checks import create_check, get_all_checks, get_latest_check
3
+ from udata_hydra.routes.checks import get_all_checks, get_latest_check
4
4
  from udata_hydra.routes.resources import (
5
5
  create_resource,
6
6
  delete_resource,
@@ -14,7 +14,6 @@ routes: list = [
14
14
  # Routes for checks
15
15
  web.get("/api/checks/latest/", get_latest_check),
16
16
  web.get("/api/checks/all/", get_all_checks),
17
- web.post("/api/checks/", create_check),
18
17
  # Routes for resources
19
18
  web.get("/api/resources/", get_resource),
20
19
  web.post("/api/resources/", create_resource),
@@ -0,0 +1,26 @@
1
+ from typing import Optional
2
+
3
+ from aiohttp import web
4
+
5
+ from udata_hydra.db.check import Check
6
+ from udata_hydra.schemas import CheckSchema
7
+ from udata_hydra.utils import get_request_params
8
+
9
+
10
+ async def get_latest_check(request: web.Request) -> web.Response:
11
+ """Get the latest check for a given URL or resource_id"""
12
+ url, resource_id = get_request_params(request, params_names=["url", "resource_id"])
13
+ data: Optional[dict] = await Check.get_latest(url, resource_id)
14
+ if not data:
15
+ raise web.HTTPNotFound()
16
+ if data["deleted"]:
17
+ raise web.HTTPGone()
18
+ return web.json_response(CheckSchema().dump(dict(data)))
19
+
20
+
21
+ async def get_all_checks(request: web.Request) -> web.Response:
22
+ url, resource_id = get_request_params(request, params_names=["url", "resource_id"])
23
+ data: Optional[list] = await Check.get_all(url, resource_id)
24
+ if not data:
25
+ raise web.HTTPNotFound()
26
+ return web.json_response([CheckSchema().dump(dict(r)) for r in data])
@@ -1,68 +0,0 @@
1
- import json
2
- from typing import Optional
3
-
4
- import aiohttp
5
- from aiohttp import web
6
- from marshmallow import ValidationError
7
-
8
- from udata_hydra import config, context
9
- from udata_hydra.crawl import RESOURCE_RESPONSE_STATUSES, check_url
10
- from udata_hydra.db.check import Check
11
- from udata_hydra.db.resource import Resource
12
- from udata_hydra.schemas import CheckSchema
13
- from udata_hydra.utils import get_request_params
14
-
15
-
16
- async def get_latest_check(request: web.Request) -> web.Response:
17
- """Get the latest check for a given URL or resource_id"""
18
- url, resource_id = get_request_params(request, params_names=["url", "resource_id"])
19
- data: Optional[dict] = await Check.get_latest(url, resource_id)
20
- if not data:
21
- raise web.HTTPNotFound()
22
- if data["deleted"]:
23
- raise web.HTTPGone()
24
- return web.json_response(CheckSchema().dump(dict(data)))
25
-
26
-
27
- async def get_all_checks(request: web.Request) -> web.Response:
28
- url, resource_id = get_request_params(request, params_names=["url", "resource_id"])
29
- data: Optional[list] = await Check.get_all(url, resource_id)
30
- if not data:
31
- raise web.HTTPNotFound()
32
- return web.json_response([CheckSchema().dump(dict(r)) for r in data])
33
-
34
-
35
- async def create_check(request: web.Request) -> web.Response:
36
- """Create a new check"""
37
-
38
- # Get resource_id from request
39
- try:
40
- payload: dict = await request.json()
41
- resource_id: str = payload["resource_id"]
42
- except ValidationError as err:
43
- raise web.HTTPBadRequest(text=json.dumps(err.messages))
44
- except KeyError as e:
45
- raise web.HTTPBadRequest(text=f"Missing key: {e}")
46
-
47
- # Get URL from resource_id
48
- try:
49
- resource: dict = await Resource.get(resource_id, "url")
50
- url: str = resource["url"]
51
- except Exception:
52
- raise web.HTTPNotFound(text=f"Couldn't find URL for resource {resource_id}")
53
-
54
- context.monitor().set_status(f'Crawling url "{url}"...')
55
-
56
- async with aiohttp.ClientSession(
57
- timeout=None, headers={"user-agent": config.USER_AGENT}
58
- ) as session:
59
- status: str = await check_url(
60
- url=url, resource_id=resource_id, session=session, worker_priority="high"
61
- )
62
- context.monitor().refresh(status)
63
-
64
- check: Optional[dict] = await Check.get_latest(url, resource_id)
65
- if not check:
66
- raise web.HTTPBadRequest(text=f"Check not created, status: {status}")
67
-
68
- return web.json_response(CheckSchema().dump(dict(check)))