djcheckup 0.2.0__tar.gz → 0.2.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: djcheckup
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: DJ Checkup is a security scanner for Django sites.
5
5
  Author: Stuart Maxwell
6
6
  Author-email: Stuart Maxwell <stuart@amanzi.nz>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "djcheckup"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  description = "DJ Checkup is a security scanner for Django sites."
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Stuart Maxwell", email = "stuart@amanzi.nz" }]
@@ -26,5 +26,15 @@ target-version = "py313"
26
26
  [tool.ruff.lint]
27
27
  select = ["ALL"]
28
28
 
29
+ [tool.ruff.lint.extend-per-file-ignores]
30
+ "tests/**" = ["S101", "ANN201", "ANN001", "ARG001"]
31
+
29
32
  [tool.ruff.lint.pydocstyle]
30
33
  convention = "google" # Accepts: "google", "numpy", or "pep257".
34
+
35
+ [dependency-groups]
36
+ dev = ["pytest>=8.4.2"]
37
+
38
+ [tool.pytest.ini_options]
39
+ pythonpath = "."
40
+ python_files = "test_*.py"
@@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
4
4
  from dataclasses import dataclass
5
5
  from enum import Enum
6
6
  from http.cookiejar import CookieJar
7
- from typing import Literal
7
+ from typing import Literal, Self
8
8
 
9
9
  import httpx
10
10
 
@@ -286,23 +286,76 @@ class SchemeCheck(_BaseCheck):
286
286
  return response.url.scheme == self.end_scheme
287
287
 
288
288
 
289
+ def create_context(url: httpx.URL, client: httpx.Client, response: httpx.Response) -> SiteCheckContext:
290
+ """Create a SiteCheckContext context object.
291
+
292
+ Args:
293
+ url: The URL to check.
294
+ client: The HTTPX client to use for requests.
295
+ response: The HTTPX response object from the initial request.
296
+
297
+ Returns:
298
+ A SiteCheckContext object containing the provided parameters.
299
+ """
300
+ return SiteCheckContext(
301
+ url=url,
302
+ client=client,
303
+ headers=response.headers,
304
+ cookies=response.cookies.jar,
305
+ content=response.text,
306
+ response_url=response.url,
307
+ )
308
+
309
+
289
310
  class SiteChecker:
290
311
  """Run all the checks for a given site."""
291
312
 
292
- def __init__(self, url: str) -> None:
293
- """Initialize the SiteChecker with a URL."""
313
+ def __init__(
314
+ self,
315
+ url: str,
316
+ *,
317
+ client: httpx.Client | None = None,
318
+ user_agent: str = "DJCheckupBot/1.0 (+https://pypi.org/project/djcheckup/)",
319
+ timeout: float = 10.0,
320
+ follow_redirects: bool = True,
321
+ ) -> None:
322
+ """Initialize the SiteChecker with a URL and optional HTTPX client.
323
+
324
+ Args:
325
+ url: The URL to check.
326
+ client: An optional HTTPX client to use for requests.
327
+ user_agent: The User-Agent string to use for requests.
328
+ timeout: The timeout for requests in seconds.
329
+ follow_redirects: Whether to follow redirects.
330
+ """
294
331
  self.url: httpx.URL = httpx.URL(url)
295
- self.user_agent: str = "DJCheckupBot/1.0 (+https://djcheckup.com/bot-info)"
296
- self.timeout: float = 10.0
297
- self.client: httpx.Client = httpx.Client(
298
- headers={"User-Agent": self.user_agent},
299
- timeout=self.timeout,
300
- follow_redirects=True,
301
- )
332
+ self._client_provided = client is not None
333
+
334
+ if client is not None:
335
+ self.client = client
336
+ else:
337
+ self.client = httpx.Client(
338
+ headers={"User-Agent": user_agent},
339
+ timeout=timeout,
340
+ follow_redirects=follow_redirects,
341
+ )
302
342
 
303
343
  def close(self) -> None:
304
- """Close the HTTP client and release resources."""
305
- self.client.close()
344
+ """Close the HTTP client and release resources.
345
+
346
+ Only close the client if it was created by this instance.
347
+ """
348
+ if not self._client_provided:
349
+ self.client.close()
350
+
351
+ # Context manager support for automatic cleanup
352
+ def __enter__(self) -> Self:
353
+ """Context manager entry."""
354
+ return self
355
+
356
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None: # noqa: ANN001
357
+ """Context manager exit."""
358
+ self.close()
306
359
 
307
360
  def run_first_check(self) -> CheckResponse:
308
361
  """Run the first check.
@@ -327,14 +380,7 @@ class SiteChecker:
327
380
  message=error_message,
328
381
  )
329
382
 
330
- self.context = SiteCheckContext(
331
- url=self.url,
332
- client=self.client,
333
- headers=response.headers,
334
- cookies=response.cookies.jar,
335
- content=response.text,
336
- response_url=response.url,
337
- )
383
+ self.context = create_context(self.url, self.client, response)
338
384
 
339
385
  return CheckResponse(
340
386
  name=check_name,
@@ -360,6 +406,4 @@ class SiteChecker:
360
406
  site_check_results.check_results.append(result)
361
407
  previous_results[check.check_id] = result
362
408
 
363
- # Close the HTTP client
364
- self.client.close()
365
409
  return site_check_results
@@ -15,8 +15,8 @@ def run_checks(url: str, output_format: Literal["json"]) -> str: ...
15
15
 
16
16
  def run_checks(url: str, output_format: Literal["object", "json"] = "object") -> str | SiteCheckResult:
17
17
  """Run the DJ Checkup tool against a specific URL and returns a JSON string."""
18
- checker = SiteChecker(url)
19
- results = checker.run_checks(all_checks)
18
+ with SiteChecker(url) as checker:
19
+ results = checker.run_checks(all_checks)
20
20
 
21
21
  if output_format not in {"object", "json"}:
22
22
  msg = f"Invalid output_format: {output_format!r}. Must be 'object' or 'json'."
File without changes
File without changes