Skip to content
This repository has been archived by the owner on Nov 17, 2022. It is now read-only.

218ify create_x_path() functions in resources.py #263

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
### Added

- [Constants] `STANDARD_VERSIONS_SUPPORTED` lists all versions of the Standard that are fully supported by pyIATI. [#223]
- [Constants] `STANDARD_VERSIONS_MINOR` lists all Minor versions of the IATI Standard. [#264]

- [Datasets] A Dataset `xml_tree` may be set with an ElementTree. [#235]

Expand Down
3 changes: 3 additions & 0 deletions iati/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
]))
"""The major versions of the IATI Standard."""

STANDARD_VERSIONS_MINOR = STANDARD_VERSIONS
"""The minor versions of the IATI Standard."""

LOG_FILE_NAME = 'iatilib.log'
"""The location of the primary IATI log file.

Expand Down
23 changes: 22 additions & 1 deletion iati/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import os
import pkg_resources
import iati.constants
import iati.utilities


PACKAGE = __name__
Expand Down Expand Up @@ -205,13 +206,20 @@ def create_codelist_path(codelist_name, version=None):
Returns:
str: The path to a file containing the specified Codelist.

Raises:
TypeError: When the codelist name is not a string.
ValueError: When an invalid version is specified.

Note:
Does not check whether the specified Codelist actually exists.

Warning:
It needs to be determined how best to locate a user-defined Codelist that is available at a URL that needs fetching.

"""
if not isinstance(codelist_name, str):
raise TypeError('The name of a Codelist must be a string, not a {0}'.format(type(codelist_name)))

if codelist_name[-4:] == FILE_CODELIST_EXTENSION:
codelist_name = codelist_name[:-4]

Expand All @@ -221,12 +229,25 @@ def create_codelist_path(codelist_name, version=None):
def create_codelist_mapping_path(version=None):
"""Determine the path of the Codelist mapping file.

version (str): The version of the Standard to return the data files for. Defaults to None. This means that the path is returned for a filename independent of any version of the Standard.
version (str): The version of the Standard to return the data files for.
Decimal: Return a path for the specified version of the Standard.
Integer: Return a path for the latest Decimal version within the given integer.

Raises:
ValueError: If an invalid version is specified.

Returns:
str: The path to a file containing the mapping file.

"""
try:
if int(version) in iati.constants.STANDARD_VERSIONS_MAJOR:
version = max(iati.utilities.versions_for_integer(version))
except ValueError:
pass # a non-major version has been specified
except (OverflowError, TypeError):
raise ValueError('The version must be a Decimal or Integer version of the IATI Standrd, not {0}'.format(version))

return path_for_version(FILE_CODELIST_MAPPING, version)


Expand Down
14 changes: 14 additions & 0 deletions iati/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,26 @@ def standard_version_major(request):
return str(request.param)


@pytest.fixture(params=iati.constants.STANDARD_VERSIONS_MINOR)
def standard_version_minor(request):
"""Return a minor version of the IATI Standard."""
return str(request.param)

@pytest.fixture(params=iati.constants.STANDARD_VERSIONS)
def standard_version_all(request):
"""Return a version of the IATI Standard."""
return request.param


@pytest.fixture(params=iati.constants.STANDARD_VERSIONS + iati.constants.STANDARD_VERSIONS_MAJOR + [None])
def standard_version_all_types(request):
"""Return a version of the IATI Standard. This includes all versions that pyIATI may support in some manner."""
if request.param is None:
return [None]

return [str(request.param)]


@pytest.fixture(params=iati.constants.STANDARD_VERSIONS_SUPPORTED)
def standard_version_optional(request):
"""Return a list that can be passed to a function using the argument list unpacking functionality.
Expand Down
14 changes: 12 additions & 2 deletions iati/tests/test_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ class TestConstants(object):

@pytest.fixture(params=[
iati.constants.STANDARD_VERSIONS,
iati.constants.STANDARD_VERSIONS_SUPPORTED
iati.constants.STANDARD_VERSIONS_SUPPORTED,
iati.constants.STANDARD_VERSIONS_MINOR
])
def standard_versions_list(self, request):
"""Return a list of Version Numbers."""
Expand All @@ -24,10 +25,15 @@ def test_nsmap(self):
assert isinstance(iati.constants.NSMAP['xsd'], str)

def test_standard_versions_all_are_numbers(self, standard_versions_list):
"""Check that each item in standard versions is a string that can be considered to be a decimal number."""
"""Check that each item in standard versions is a string that can be considered to be a correctly formatted decimal number."""
for version in standard_versions_list:
split_version = version.split('.')

assert isinstance(version, str)
assert float(version)
assert len(split_version) == 2
assert len(split_version[1]) == 2
assert version == version.strip()

def test_standard_versions_correct_format(self, standard_versions_list):
"""Check that standard versions is in the correct format."""
Expand All @@ -49,3 +55,7 @@ def test_standard_versions_major_all_are_integers(self):
def test_standard_versions_major_correct_number(self):
"""Check that the correct number of major versions are detected."""
assert len(iati.constants.STANDARD_VERSIONS_MAJOR) == 2

def test_standard_versions_minor_correct_number(self):
"""Check that the correct number of minor versions are detected."""
assert len(iati.constants.STANDARD_VERSIONS_MINOR) == 7
74 changes: 65 additions & 9 deletions iati/tests/test_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import iati.constants
import iati.resources
import iati.validator
import iati.utilities
import iati.tests.resources


Expand Down Expand Up @@ -87,6 +88,70 @@ def test_get_test_data_paths_in_folder(self, version, expected_num_paths):
assert len(paths) == expected_num_paths


class TestResourceCreatePath(object):
"""A container for tests relating to creating paths."""

@pytest.mark.parametrize('cl_name', [
'AidType', 'FlowType', 'Language', # Codelist names that are valid at all versions
'BudgetStatus', 'OtherIdentifierType', 'PolicyMarkerVocabulary', # Codelist names that are valid at some versions, but not all
'invalid-codelist-name' # Codelist name that is not a valid Codelist
])
def test_create_codelist_path(self, cl_name, standard_version_all_types):
"""Check that a Codelist path is correctly created."""
path = iati.resources.create_codelist_path(cl_name, *standard_version_all_types)

assert isinstance(path, str)
assert iati.resources.folder_name_for_version(*standard_version_all_types) in path

@pytest.mark.parametrize("not_a_str", iati.tests.utilities.generate_test_types(['none', 'str'], True))
def test_create_codelist_path_non_str_name(self, not_a_str, standard_version_all_types):
"""Check that a Error is raised when requesting a Codelist with a non-string name."""
with pytest.raises(TypeError):
iati.resources.create_codelist_path(not_a_str, *standard_version_all_types)

@pytest.mark.parametrize("not_a_version", iati.tests.utilities.generate_test_types(['none'], True))
def test_create_codelist_path_fuzzed_version(self, not_a_version):
"""Check that a ValueError is raised when requesting a Codelist with a fuzzed version."""
with pytest.raises(ValueError):
iati.resources.create_codelist_path('a-name-for-a-codelist', not_a_version)

def test_create_codelist_mapping_path_minor(self, standard_version_minor):
"""Check that there is a single Codelist Mapping File for minor versions."""
path = iati.resources.create_codelist_mapping_path(standard_version_minor)

assert isinstance(path, str)
assert iati.resources.folder_name_for_version(standard_version_minor) in path

def test_create_codelist_mapping_path_major(self, standard_version_major):
"""Check that requesting a Codelist Mapping File for a major version returns the same path as for the last minor within the major."""
standard_version_minor = max(iati.utilities.versions_for_integer(standard_version_major))

path_major = iati.resources.create_codelist_mapping_path(standard_version_major)
path_minor = iati.resources.create_codelist_mapping_path(standard_version_minor)

assert path_major == path_minor

def test_create_codelist_mapping_path_version_independent(self):
"""Check that a ValueError is raised when requesting a version-independent Codelist Mapping File."""
with pytest.raises(ValueError):
iati.resources.create_codelist_mapping_path()

@pytest.mark.parametrize("not_a_version", iati.tests.utilities.generate_test_types(['none'], True))
def test_create_codelist_mapping_path_invalid_value(self, not_a_version):
"""Check that a ValueError is raised when requesting a fuzzed Codelist Mapping File."""
with pytest.raises(ValueError):
iati.resources.create_codelist_mapping_path(not_a_version)

def test_create_codelist_mapping_path_is_xml(self, standard_version_optional):
"""Check that the Codelist Mapping File path points to a valid XML file."""
path = iati.resources.create_codelist_mapping_path(*standard_version_optional)

content = iati.utilities.load_as_string(path)

assert len(content) > 5000
assert iati.validator.is_xml(content)


class TestResourceLibraryData(object):
"""A container for tests relating to pyIATI resources."""

Expand Down Expand Up @@ -142,15 +207,6 @@ def test_get_codelist_mapping_paths(self, standard_version_optional):

assert len(codelist_mapping_paths) == 1

def test_create_codelist_mapping_path(self, standard_version_optional):
"""Check that the Codelist Mapping File path points to a valid XML file."""
path = iati.resources.create_codelist_mapping_path(*standard_version_optional)

content = iati.utilities.load_as_string(path)

assert len(content) > 5000
assert iati.validator.is_xml(content)


class TestResourceRulesets(object):
"""A container for tests relating to Ruleset resources."""
Expand Down