diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 03b0df7..0b7f53d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,4 @@ +exclude: courts_schema_types_autogenerated\.py repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 @@ -14,11 +15,44 @@ repos: - id: no-commit-to-branch - id: trailing-whitespace + # There is a bug with upstream which means the imports are + # not sorted -- this should do for now + - repo: https://github.com/camptocamp/jsonschema-gentypes + rev: 2.8.1 + hooks: + - id: jsonschema-gentypes + files: src/ds_caselaw_utils/data/schema/courts.schema.json + language_version: python3.9 + - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.6.8 hooks: + - id: ruff + args: + - --fix + - --exit-non-zero-on-fix - id: ruff-format + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.11.2 + hooks: + - id: mypy + name: mypy-src + additional_dependencies: + - types-PyYAML + - ruamel.yaml + args: + - --strict + files: ^(src/ds_caselaw_utils|scripts) + exclude: (?:test_|factory.py) + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.11.2 + hooks: + - id: mypy + name: mypy-tests + files: ^src/ds_caselaw_utils/test_ + - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.29.2 hooks: diff --git a/jsonschema-gentypes.yaml b/jsonschema-gentypes.yaml new file mode 100644 index 0000000..e28a7d4 --- /dev/null +++ b/jsonschema-gentypes.yaml @@ -0,0 +1,22 @@ +headers: > + # Automatically generated file from a JSON schema +# Used to correctly format the generated file +# callbacks: +# - - black +# - - isort +generate: + - # JSON schema file path + source: src/ds_caselaw_utils/data/schema/courts.schema.json + # Python file path + destination: src/ds_caselaw_utils/courts_schema_types_autogenerated.py + # The name of the root element + root_name: RawCourtRepository + # Argument passed to the API + api_arguments: + additional_properties: Only explicit + # Rename an element + name_mapping: {} + # The minimum Python version that the code should support. By default the + # currently executing Python version is chosen. Note that the output + # may require typing_extensions to be installed. + python_version: "3.9" diff --git a/poetry.lock b/poetry.lock index e77f182..432204b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -77,7 +77,18 @@ files = [ {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, ] +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "3fb6d123d16d4dc7dff8a211f0648dc9be709413ce06faad3ef74ebf9b42196d" +content-hash = "7ec28941ce5162e1ce2d139d978110f601b17e5d3c4b77cbf52f753a8674e378" diff --git a/pyproject.toml b/pyproject.toml index 6e5ba39..d99eb0f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.9" "ruamel.yaml" = "^0.18.0" +typing-extensions = "^4.12.2" [tool.poetry.dev-dependencies] @@ -19,26 +20,15 @@ python = "^3.9" line-length = 120 [tool.ruff.lint] -ignore = ["E501", "G004", "PLR2004", "RUF005", "RUF012", "UP040"] # long lines, fstrings in logs, magic values, consider not concat, mutable classbits, type instead of TypeAlias -extend-select = ["W", "B", "Q", "C90", "I", "UP", "YTT", "ASYNC", "S", "BLE", "A", "COM", "C4", "DTZ", "T10", "DJ", "EM", "EXE", "FA", - "ISC", "ICN", "G", "INP", "PIE", "T20", "PYI", "PT", "Q", "RSE", "RET", "SLF", "SLOT", "SIM", "TID", "TCH", "INT", "PTH", - "FIX", "PGH", "PL", "TRY", "FLY", "PERF", "RUF"] +ignore = ["E501", "G004", "PLR2004", "RUF005", "RUF012", "UP040"] # longlines, fstrings in logs, magic values, consider not concat, mutable classbits, type instead of TypeAlias +extend-select = ["W", "I", ] +# extend-select = [ "SLF", "SIM", "B", "Q", "C90", "I", "UP", "YTT", "ASYNC", "S", "BLE", "A", "COM", "C4", "DTZ", "T10", "DJ", "EM", "EXE", "FA", +# "ISC", "ICN", "G", "INP", "PIE", "T20", "PYI", "PT", "Q", "RSE", "RET", "SLOT", "TID", "TCH", "INT", "PTH", +# "FIX", "PGH", "PL", "TRY", "FLY", "PERF", "RUF"] unfixable = ["ERA"] -# things skipped: -# N: naming, possibly good -# D: docstrings missing throughout -# ANN: annotations missing throughout -# FBT: not convinced boolean trap worth auto-banning. -# CPY: copyright at top of each file -# G: logging warnings -- fstrings bad? -# ARG: sometimes you need to accept arguments. -# TD: somewhat finicky details about formatting TODOs -# FIX: flags todos: possible to add -- skipped for now -# ERA: lots of false positives, not a good autofix -# PD, NPY, AIR: ignored, panda / numpy / airflow specific -# FURB: not yet out of preview - +[tool.ruff.lint.extend-per-file-ignores] +"test_*" = ["S101"] # `assert` is fine in tests [tool.commitizen] name = "cz_conventional_commits" diff --git a/scripts/build_courts_list b/scripts/build_courts_list index 709da5e..0d19583 100755 --- a/scripts/build_courts_list +++ b/scripts/build_courts_list @@ -1,6 +1,7 @@ #!/usr/bin/env python from pathlib import Path + import yaml print("Loading courts...") diff --git a/src/ds_caselaw_utils/__init__.py b/src/ds_caselaw_utils/__init__.py index 8ae00da..f8d9bd7 100644 --- a/src/ds_caselaw_utils/__init__.py +++ b/src/ds_caselaw_utils/__init__.py @@ -1,2 +1,4 @@ from .courts import courts -from .neutral import neutral_url as neutral_url +from .neutral import neutral_url + +__all__ = ["courts", "neutral_url"] diff --git a/src/ds_caselaw_utils/courts.py b/src/ds_caselaw_utils/courts.py index e1a0884..952721a 100644 --- a/src/ds_caselaw_utils/courts.py +++ b/src/ds_caselaw_utils/courts.py @@ -1,97 +1,122 @@ +# mypy: disable-error-code="override" + """ Get metada data for the courts covered by the service """ import pathlib from datetime import date +from typing import NewType, Optional from ruamel.yaml import YAML +from ds_caselaw_utils.courts_schema_types_autogenerated import ( + RawCourt, + RawCourtRepository, + RawJurisdiction, +) + +CourtCode = NewType("CourtCode", str) +CourtParam = NewType("CourtParam", str) +JurisdictionCode = NewType("JurisdictionCode", str) + class Jurisdiction: - def __init__(self, data): - self.code = data.get("code") - self.name = data.get("name") - self.prefix = data.get("prefix") + def __init__(self, data: RawJurisdiction): + self.code: JurisdictionCode = JurisdictionCode(data["code"]) + self.name = data["name"] + self.prefix: Optional[str] = data.get("prefix") class Court: - def __init__(self, data): - self.code = data.get("code") - self.name = data.get("name") - self.grouped_name = data.get("grouped_name") or data.get("name") - self.link = data.get("link") - self.ncn = data.get("ncn") - self.canonical_param = data.get("param") - self.param_aliases = [data.get("param")] + (data.get("extra_params") or []) - self.start_year = data.get("start_year") - self.end_year = data.get("end_year") or date.today().year - self.jurisdictions = [Jurisdiction(jurisdiction_data) for jurisdiction_data in data.get("jurisdictions", [])] - - def get_jurisdiction(self, code): + def __init__(self, data: RawCourt) -> None: + self.canonical_param: Optional[CourtParam] + self.param_aliases: list[CourtParam] + self.code: CourtCode = CourtCode(data["code"]) + self.name: str = data["name"] + self.grouped_name: str = data.get("grouped_name") or data["name"] + self.link: str = data["link"] + self.ncn: Optional[str] = data.get("ncn") + if "param" in data: + self.canonical_param = CourtParam(data["param"]) + self.param_aliases = [CourtParam(data["param"])] + [ + CourtParam(extra_param) for extra_param in data.get("extra_params", []) + ] + else: + self.canonical_param = None + self.param_aliases = [] + + self.start_year: Optional[int] = data.get("start_year") + self.end_year: int = data.get("end_year") or date.today().year + self.jurisdictions: list[Jurisdiction] = [ + Jurisdiction(jurisdiction_data) for jurisdiction_data in data.get("jurisdictions", []) + ] + + def get_jurisdiction(self, code: str) -> Optional[Jurisdiction]: return next((j for j in self.jurisdictions if j.code == code), None) - def expand_jurisdictions(self): + def expand_jurisdictions(self) -> list["Court"]: return [self] + [CourtWithJurisdiction(self, jurisdiction) for jurisdiction in self.jurisdictions] - def __repr__(self): + def __repr__(self) -> str: return self.name class CourtWithJurisdiction(Court): - def __init__(self, court, jurisdiction): - self.court = court - self.jurisdiction = jurisdiction - self.jurisdictions = [] + def __init__(self, court: Court, jurisdiction: Jurisdiction) -> None: + self.court: Court = court + self.jurisdiction: Jurisdiction = jurisdiction + self.jurisdictions: list[Jurisdiction] = [] @property - def code(self): - return "/".join((self.court.code, self.jurisdiction.code)) + def code(self) -> CourtCode: + return CourtCode("/".join((self.court.code, self.jurisdiction.code))) @property - def name(self): + def name(self) -> str: return "%s – %s" % (self.court.name, self.jurisdiction.name) @property - def grouped_name(self): + def grouped_name(self) -> str: return self.court.grouped_name @property - def link(self): + def link(self) -> str: return self.court.link @property - def ncn(self): + def ncn(self) -> Optional[str]: return self.court.ncn @property - def canonical_param(self): + def canonical_param(self) -> Optional[CourtParam]: return self.court.canonical_param @property - def param_aliases(self): + def param_aliases(self) -> list[CourtParam]: return self.court.param_aliases @property - def start_year(self): + def start_year(self) -> Optional[int]: return self.court.start_year @property - def end_year(self): + def end_year(self) -> int: return self.court.end_year @property - def jurisdiction_prefix(self): + def jurisdiction_prefix(self) -> Optional[str]: return self.jurisdiction.prefix class CourtGroup: - def __init__(self, name, courts): - self.name = name - self.courts = courts + def __init__(self, name: Optional[str], courts: list[Court]) -> None: + self.name: Optional[str] = name + self.courts: list[Court] = courts @property - def display_heading(self): + def display_heading(self) -> bool: + """Is this group displayed in the PUI?""" return self.name is not None @@ -100,29 +125,32 @@ class CourtNotFoundException(Exception): class CourtsRepository: - def __init__(self, data): + def __init__(self, data: RawCourtRepository): self._data = data self._byParam = {} self._byCode = {} for group in self._data: - for courtData in group.get("courts"): + for courtData in group.get("courts", []): court = Court(courtData) - self._byParam[courtData.get("param")] = court - self._byCode[courtData.get("code")] = court + if "param" in courtData: + self._byParam[courtData.get("param")] = court + self._byCode[courtData["code"]] = court - def get_by_param(self, param): + def get_by_param(self, param: CourtParam) -> Court: try: return self._byParam[param] except KeyError: raise CourtNotFoundException() - def get_court_by_code(self, code): + def get_court_by_code(self, code: CourtCode) -> Court: try: return self._byCode[code] except KeyError: raise CourtNotFoundException() - def get_court_with_jurisdiction_by_code(self, court_code, jursidiction_code): + def get_court_with_jurisdiction_by_code( + self, court_code: CourtCode, jursidiction_code: JurisdictionCode + ) -> CourtWithJurisdiction: court = self.get_court_by_code(court_code) if court is None: raise CourtNotFoundException() @@ -131,77 +159,77 @@ def get_court_with_jurisdiction_by_code(self, court_code, jursidiction_code): raise CourtNotFoundException() return CourtWithJurisdiction(court, jurisdiction) - def get_by_code(self, code): + def get_by_code(self, code: CourtCode) -> Court: if "/" in code: (court_code, jurisdiction_code) = code.split("/") - return self.get_court_with_jurisdiction_by_code(court_code, jurisdiction_code) + return self.get_court_with_jurisdiction_by_code(CourtCode(court_code), JurisdictionCode(jurisdiction_code)) else: return self.get_court_by_code(code) - def get_all(self, with_jurisdictions=False): - courts = [Court(court) for category in self._data for court in category.get("courts")] + def get_all(self, with_jurisdictions: bool = False) -> list[Court]: + courts = [Court(court) for category in self._data for court in category.get("courts", [])] if with_jurisdictions: return [c for court in courts for c in court.expand_jurisdictions()] else: return courts - def get_selectable(self): + def get_selectable(self) -> list[Court]: courts = [] for category in self._data: - for court in category.get("courts"): - if court.get("selectable"): + for court in category.get("courts", []): + if court["selectable"]: courts.append(Court(court)) return courts - def get_selectable_groups(self): + def get_selectable_groups(self) -> list[CourtGroup]: groups = [] for category in self._data: - courts = [Court(court) for court in category.get("courts") if court.get("selectable")] + courts = [Court(court) for court in category.get("courts", []) if court["selectable"]] if len(courts) > 0: groups.append(CourtGroup(category.get("display_name"), courts)) return groups - def get_grouped_selectable_courts(self): + def get_grouped_selectable_courts(self) -> list[CourtGroup]: groups = [] for category in self._data: if not category.get("is_tribunal"): - courts = [Court(court) for court in category.get("courts") if court.get("selectable")] + courts = [Court(court) for court in category.get("courts", []) if court["selectable"]] if len(courts) > 0: groups.append(CourtGroup(category.get("display_name"), courts)) return groups - def get_grouped_selectable_tribunals(self): + def get_grouped_selectable_tribunals(self) -> list[CourtGroup]: groups = [] for category in self._data: if category.get("is_tribunal"): - courts = [Court(court) for court in category.get("courts") if court.get("selectable")] + courts = [Court(court) for court in category.get("courts", []) if court["selectable"]] if len(courts) > 0: groups.append(CourtGroup(category.get("display_name"), courts)) return groups - def get_listable_groups(self): + def get_listable_groups(self) -> list[CourtGroup]: groups = [] for category in self._data: - courts = [Court(court) for court in category.get("courts") if court.get("listable")] + courts = [Court(court) for court in category.get("courts", []) if court["listable"]] if len(courts) > 0: groups.append(CourtGroup(category.get("display_name"), courts)) return groups - def get_listable_courts(self): + def get_listable_courts(self) -> list[Court]: courts = [] for group in self._data: if not group.get("is_tribunal"): for court in group.get("courts", []): - if court.get("listable"): + if court["listable"]: courts.append(Court(court)) return courts - def get_listable_tribunals(self): + def get_listable_tribunals(self) -> list[Court]: courts = [] for group in self._data: if group.get("is_tribunal"): for court in group.get("courts", []): - if court.get("listable"): + if court["listable"]: courts.append(Court(court)) return courts @@ -209,6 +237,6 @@ def get_listable_tribunals(self): yaml = YAML() datafile = pathlib.Path(__file__).parent / "data/court_names.yaml" with open(datafile) as f: - court_data = yaml.load(f) + court_data: RawCourtRepository = yaml.load(f) courts = CourtsRepository(court_data) diff --git a/src/ds_caselaw_utils/courts_schema_types_autogenerated.py b/src/ds_caselaw_utils/courts_schema_types_autogenerated.py new file mode 100644 index 0000000..ee1b204 --- /dev/null +++ b/src/ds_caselaw_utils/courts_schema_types_autogenerated.py @@ -0,0 +1,89 @@ +# Automatically generated file from a JSON schema + + +from typing import List, TypedDict, Union +from typing_extensions import Required + + +class RawCourt(TypedDict, total=False): + """ Raw Court. """ + + code: Required[str] + """ + pattern: ^[A-Za-z]{2,}(-[A-Za-z0-9]+)*$ + + Required property + """ + + name: Required[str] + """ Required property """ + + grouped_name: str + param: str + """ pattern: ^[a-z]{2,}(?:/[a-z0-9]+)?$ """ + + extra_params: List["_RawCourtExtraParamsItem"] + ncn: str + link: Required[str] + """ + format: uri + + Required property + """ + + start_year: int + """ minimum: 1066 """ + + end_year: int + listable: Required[bool] + """ Required property """ + + selectable: Required[bool] + """ Required property """ + + jurisdictions: List["RawJurisdiction"] + + +class RawCourtGroup(TypedDict, total=False): + """ Raw Court Group. """ + + name: Required[str] + """ Required property """ + + display_name: Required[Union[str, None]] + """ Required property """ + + is_tribunal: Required[bool] + """ Required property """ + + courts: Required[List["RawCourt"]] + """ Required property """ + + + +RawCourtRepository = List["RawCourtGroup"] +""" +Raw Court List. + +A list of courts +""" + + + +class RawJurisdiction(TypedDict, total=False): + """ Raw Jurisdiction. """ + + prefix: Required[str] + """ Required property """ + + name: Required[str] + """ Required property """ + + code: Required[str] + """ Required property """ + + + +_RawCourtExtraParamsItem = str +""" pattern: ^[a-z]{2,}(/[a-z]+)?$ """ + diff --git a/src/ds_caselaw_utils/data/schema/courts.schema.json b/src/ds_caselaw_utils/data/schema/courts.schema.json index 4fbbb58..c518a87 100644 --- a/src/ds_caselaw_utils/data/schema/courts.schema.json +++ b/src/ds_caselaw_utils/data/schema/courts.schema.json @@ -1,11 +1,12 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schema.caselaw.nationalarchives.gov.uk/courts.schema.json", - "title": "Courts", + "title": "Raw Court List", "description": "A list of courts", "type": "array", "items": { "type": "object", + "title": "Raw Court Group", "properties": { "name": { "type": "string" @@ -20,6 +21,7 @@ "type": "array", "items": { "type": "object", + "title": "Raw Court", "properties": { "code": { "type": "string", @@ -65,6 +67,7 @@ "jurisdictions": { "type": "array", "items": { + "title": "Raw Jurisdiction", "type": "object", "properties": { "prefix": { @@ -76,7 +79,9 @@ "code": { "type": "string" } - } + }, + "required": ["prefix", "name", "code"], + "additionalProperties": false } } }, diff --git a/src/ds_caselaw_utils/factory.py b/src/ds_caselaw_utils/factory.py new file mode 100644 index 0000000..c383fb2 --- /dev/null +++ b/src/ds_caselaw_utils/factory.py @@ -0,0 +1,32 @@ +import typing + +from .courts import Court +from .courts_schema_types_autogenerated import RawCourt, RawCourtRepository + + +class CourtFactory(Court): + def __init__(self, data): + data = make_court_valid(data) + super().__init__(data) + + +def make_court_valid(data) -> RawCourt: + for keyword in ["code", "name", "link"]: + if keyword not in data: + data[keyword] = f"placeholder {keyword}" + for keyword in ["selectable", "listable"]: + if keyword not in data: + data[keyword] = True + + return typing.cast(RawCourt, data) + + +def make_court_repo_valid(data) -> RawCourtRepository: + new_court_groups = [] + for court_group in data: + new_courts = [] + for court in court_group["courts"]: + new_courts.append(make_court_valid(court)) + court_group["court"] = new_courts + new_court_groups.append(court_group) + return typing.cast(RawCourtRepository, new_court_groups) diff --git a/src/ds_caselaw_utils/neutral.py b/src/ds_caselaw_utils/neutral.py index 411f653..8eb494e 100644 --- a/src/ds_caselaw_utils/neutral.py +++ b/src/ds_caselaw_utils/neutral.py @@ -4,6 +4,7 @@ import pathlib import re +from typing import Optional from ruamel.yaml import YAML @@ -13,7 +14,7 @@ citation_data = yaml.load(f) -def neutral_url(citation): +def neutral_url(citation: str) -> Optional[str]: """Given a neutral citation such as `[2020] EAT 17`, return a public-API URL like `/eat/2020/17`, or None if no match is found. diff --git a/src/ds_caselaw_utils/py.typed b/src/ds_caselaw_utils/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/ds_caselaw_utils/test_courts.py b/src/ds_caselaw_utils/test_courts.py index 5dfdb29..f7f8354 100644 --- a/src/ds_caselaw_utils/test_courts.py +++ b/src/ds_caselaw_utils/test_courts.py @@ -5,10 +5,14 @@ from ruamel.yaml import YAML +from ds_caselaw_utils.factory import CourtFactory, make_court_repo_valid + from .courts import ( Court, + CourtCode, CourtGroup, CourtNotFoundException, + CourtParam, CourtsRepository, CourtWithJurisdiction, courts, @@ -30,10 +34,11 @@ def test_loads_all_courts_without_jurisdictions(self): { "name": "court_group", "display_name": "court group 1", - "courts": [{"name": "court1", "jurisdictions": [{"name": "jurisdiction1"}]}], + "courts": [{"name": "court1", "jurisdictions": [{"name": "jurisdiction1", "code": "code"}]}], } ] - repo = CourtsRepository(data) + valid_data = make_court_repo_valid(data) + repo = CourtsRepository(valid_data) courts = repo.get_all() self.assertIn("court1", [c.name for c in courts]) self.assertNotIn("court1 – jurisdiction1", [c.name for c in courts]) @@ -43,10 +48,11 @@ def test_loads_all_courts_with_jurisdictions(self): { "name": "court_group", "display_name": "court group 1", - "courts": [{"name": "court1", "jurisdictions": [{"name": "jurisdiction1"}]}], + "courts": [{"name": "court1", "jurisdictions": [{"name": "jurisdiction1", "code": "code"}]}], } ] - repo = CourtsRepository(data) + valid_data = make_court_repo_valid(data) + repo = CourtsRepository(valid_data) courts = repo.get_all(with_jurisdictions=True) self.assertIn("court1", [c.name for c in courts]) self.assertIn("court1 – jurisdiction1", [c.name for c in courts]) @@ -70,7 +76,8 @@ def test_loads_selectable_courts(self): "courts": [{"name": "court3", "selectable": False}], }, ] - repo = CourtsRepository(data) + valid_data = make_court_repo_valid(data) + repo = CourtsRepository(valid_data) selectable = repo.get_selectable() self.assertIn("court1", [c.name for c in selectable]) self.assertNotIn("court2", [c.name for c in selectable]) @@ -100,7 +107,8 @@ def test_loads_listable_courts(self): "courts": [{"name": "court3", "listable": False}], }, ] - repo = CourtsRepository(data) + valid_data = make_court_repo_valid(data) + repo = CourtsRepository(valid_data) groups = repo.get_listable_groups() self.assertIn("court group 1", [g.name for g in groups]) self.assertNotIn("court group 2", [g.name for g in groups]) @@ -119,8 +127,9 @@ def test_loads_court_by_param(self): "courts": [{"param": "court2", "name": "Court 2"}], }, ] - repo = CourtsRepository(data) - self.assertEqual("Court 2", repo.get_by_param("court2").name) + valid_data = make_court_repo_valid(data) + repo = CourtsRepository(valid_data) + self.assertEqual("Court 2", repo.get_by_param(CourtParam("court2")).name) def test_raises_on_unknown_court_param(self): data = [ @@ -133,7 +142,8 @@ def test_raises_on_unknown_court_param(self): "courts": [{"param": "court2", "name": "Court 2"}], }, ] - repo = CourtsRepository(data) + valid_data = make_court_repo_valid(data) + repo = CourtsRepository(valid_data) self.assertRaises(CourtNotFoundException, repo.get_by_param, "court3") def test_loads_court_by_code(self): @@ -147,8 +157,9 @@ def test_loads_court_by_code(self): "courts": [{"code": "court2", "name": "Court 2"}], }, ] - repo = CourtsRepository(data) - self.assertEqual("Court 2", repo.get_by_code("court2").name) + valid_data = make_court_repo_valid(data) + repo = CourtsRepository(valid_data) + self.assertEqual("Court 2", repo.get_by_code(CourtCode("court2")).name) def test_loads_court_with_jurisdiction_by_code(self): data = [ @@ -163,8 +174,9 @@ def test_loads_court_with_jurisdiction_by_code(self): ], } ] - repo = CourtsRepository(data) - self.assertEqual("Court 1 – Jurisdiction 1", repo.get_by_code("court1/jurisdiction1").name) + valid_data = make_court_repo_valid(data) + repo = CourtsRepository(valid_data) + self.assertEqual("Court 1 – Jurisdiction 1", repo.get_by_code(CourtCode("court1/jurisdiction1")).name) def test_raises_error_for_nonexistent_jurisdictions(self): data = [ @@ -179,7 +191,8 @@ def test_raises_error_for_nonexistent_jurisdictions(self): ], } ] - repo = CourtsRepository(data) + valid_data = make_court_repo_valid(data) + repo = CourtsRepository(valid_data) self.assertRaises(CourtNotFoundException, repo.get_by_code, "court1/jurisdiction2") self.assertRaises(CourtNotFoundException, repo.get_by_code, "court2/jurisdiction1") @@ -194,7 +207,8 @@ def test_raises_on_unknown_court_code(self): "courts": [{"code": "court2", "name": "Court 2"}], }, ] - repo = CourtsRepository(data) + valid_data = make_court_repo_valid(data) + repo = CourtsRepository(valid_data) self.assertRaises(CourtNotFoundException, repo.get_by_code, "court3") def test_returns_listable_courts(self): @@ -213,7 +227,8 @@ def test_returns_listable_courts(self): "courts": [{"param": "court3", "listable": True, "name": "Court 3"}], }, ] - repo = CourtsRepository(data) + valid_data = make_court_repo_valid(data) + repo = CourtsRepository(valid_data) self.assertIn("court1", [c.canonical_param for c in repo.get_listable_courts()]) self.assertNotIn("court2", [c.canonical_param for c in repo.get_listable_courts()]) self.assertNotIn("court3", [c.canonical_param for c in repo.get_listable_courts()]) @@ -236,7 +251,8 @@ def test_returns_listable_tribunals(self): ], }, ] - repo = CourtsRepository(data) + valid_data = make_court_repo_valid(data) + repo = CourtsRepository(valid_data) self.assertNotIn("court1", [c.canonical_param for c in repo.get_listable_tribunals()]) self.assertNotIn("court2", [c.canonical_param for c in repo.get_listable_tribunals()]) self.assertIn("court3", [c.canonical_param for c in repo.get_listable_tribunals()]) @@ -269,7 +285,8 @@ def test_returns_grouped_selectable_courts(self): ], }, ] - repo = CourtsRepository(data) + valid_data = make_court_repo_valid(data) + repo = CourtsRepository(valid_data) groups = repo.get_grouped_selectable_courts() self.assertIn("Court group", [g.name for g in groups]) self.assertNotIn("Tribunal group", [g.name for g in groups]) @@ -305,7 +322,8 @@ def test_returns_grouped_selectable_tribunals(self): ], }, ] - repo = CourtsRepository(data) + valid_data = make_court_repo_valid(data) + repo = CourtsRepository(valid_data) groups = repo.get_grouped_selectable_tribunals() self.assertIn("Tribunal group", [g.name for g in groups]) self.assertNotIn("Court group", [g.name for g in groups]) @@ -316,42 +334,43 @@ def test_returns_grouped_selectable_tribunals(self): class TestCourt(unittest.TestCase): def test_repr_string(self): - court = Court({"name": "court_name"}) + court = CourtFactory({"name": "court_name"}) self.assertEqual("court_name", str(court)) self.assertEqual("court_name", repr(court)) def test_grouped_name_explicit(self): - court = Court({"grouped_name": "court_name"}) + court = CourtFactory({"grouped_name": "court_name"}) self.assertEqual("court_name", court.grouped_name) def test_grouped_name_default(self): - court = Court({"name": "court_name"}) + court = CourtFactory({"name": "court_name"}) self.assertEqual("court_name", court.grouped_name) def test_param_aliases(self): - court = Court({"param": "param_1", "extra_params": ["param_2"]}) + court = CourtFactory({"param": "param_1", "extra_params": ["param_2"]}) self.assertEqual(["param_1", "param_2"], court.param_aliases) def test_end_year_explicit(self): - court = Court({"end_year": 1983}) + court = CourtFactory({"end_year": 1983}) self.assertEqual(1983, court.end_year) def test_end_year_default(self): - court = Court({}) + court = CourtFactory({}) self.assertEqual(date.today().year, court.end_year) def test_get_jurisdiction(self): - court = Court({"jurisdictions": [{"code": "jurisdiction1", "name": "Jurisdiction 1"}]}) + court = CourtFactory({"jurisdictions": [{"code": "jurisdiction1", "name": "Jurisdiction 1"}]}) jurisdiction = court.get_jurisdiction("jurisdiction1") + assert jurisdiction self.assertEqual("Jurisdiction 1", jurisdiction.name) def test_get_nonexistent_jurisdiction(self): - court = Court({"jurisdictions": [{"code": "jurisdiction1", "name": "Jurisdiction 1"}]}) + court = CourtFactory({"jurisdictions": [{"code": "jurisdiction1", "name": "Jurisdiction 1"}]}) jurisdiction = court.get_jurisdiction("jurisdiction2") self.assertIsNone(jurisdiction) def test_expand_jurisdictions(self): - court = Court( + court = CourtFactory( { "name": "Court 1", "jurisdictions": [{"code": "jurisdiction1", "name": "Jurisdiction 1"}], diff --git a/src/ds_caselaw_utils/test_factory.py b/src/ds_caselaw_utils/test_factory.py new file mode 100644 index 0000000..70c0490 --- /dev/null +++ b/src/ds_caselaw_utils/test_factory.py @@ -0,0 +1,84 @@ +from .factory import make_court_repo_valid + + +def test_factory(): + input_data = [ + { + "name": "court_group", + "display_name": "court group 1", + "courts": [ + { + "name": "court1", + "selectable": True, + }, + {"name": "court2", "selectable": False}, + ], + }, + { + "name": "court_group2", + "display_name": "court group 2", + "courts": [{"name": "court3", "selectable": False}], + }, + ] + + output_data = [ + { + "name": "court_group", + "display_name": "court group 1", + "courts": [ + { + "name": "court1", + "selectable": True, + "code": "placeholder code", + "link": "placeholder link", + "listable": True, + }, + { + "name": "court2", + "selectable": False, + "code": "placeholder code", + "link": "placeholder link", + "listable": True, + }, + ], + "court": [ + { + "name": "court1", + "selectable": True, + "code": "placeholder code", + "link": "placeholder link", + "listable": True, + }, + { + "name": "court2", + "selectable": False, + "code": "placeholder code", + "link": "placeholder link", + "listable": True, + }, + ], + }, + { + "name": "court_group2", + "display_name": "court group 2", + "courts": [ + { + "name": "court3", + "selectable": False, + "code": "placeholder code", + "link": "placeholder link", + "listable": True, + } + ], + "court": [ + { + "name": "court3", + "selectable": False, + "code": "placeholder code", + "link": "placeholder link", + "listable": True, + } + ], + }, + ] + assert str(output_data) == str(make_court_repo_valid(input_data))