Repository: qiskit-community/ecosystem (python)

Issue:
Configuration: language version support
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues to confirm this idea does not exist. -->

### What is the expected behavior?
Add language support for configuration

For example: for Python it would be 3.6, 3.7 etc


```json
{
     "language": {
           "name": "python",
           "versions": ["3.6", "3.7"]
     }
}
```

Pull request:
Issue 47 | Config: add language version support
<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add a release note file using reno if the change needs to be
  documented in the release notes.
⚠️ If your pull request fixes an open issue, please link to the issue.

- [ ] I have added the tests to cover my changes.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the CONTRIBUTING document.
-->

### Summary
Add language version support

### Details and comments
Closes #47 

Patch:
diff --git a/ecosystem/models/__init__.py b/ecosystem/models/__init__.py
index 4a0b9ff..4eee27b 100644
--- a/ecosystem/models/__init__.py
+++ b/ecosystem/models/__init__.py
@@ -1,6 +1,10 @@
 """Models for ecosystem."""
 
-from .configuration import RepositoryConfiguration, PythonRepositoryConfiguration
+from .configuration import (
+    RepositoryConfiguration,
+    PythonRepositoryConfiguration,
+    PythonLanguageConfiguration,
+)
 from .execution_summary import CommandExecutionSummary
 from .test_type import TestType
 from .test_results import StyleResult, TestResult, CoverageResult
diff --git a/ecosystem/models/configuration.py b/ecosystem/models/configuration.py
index 8b11d86..d2e31a3 100644
--- a/ecosystem/models/configuration.py
+++ b/ecosystem/models/configuration.py
@@ -22,12 +22,43 @@ class Languages:
         return "Languages({})".format(",".join(self.all()))
 
 
+class LanguageConfiguration(JsonSerializable):
+    """Language configuration class."""
+
+    def __init__(self, name: str, versions: List[str]):
+        """Language configuration.
+
+        Args:
+            name: programming language
+            versions: versions of programming languages
+        """
+        self.name = name
+        self.versions = versions
+
+    @classmethod
+    def default_version(cls) -> List[str]:
+        """Default language versions."""
+        return []
+
+
+class PythonLanguageConfiguration(LanguageConfiguration):
+    """Python language config."""
+
+    def __init__(self, versions: Optional[List[str]] = None):
+        versions = versions or ["3.6", "3.7", "3.8", "3.9"]
+        super().__init__(name=Languages.PYTHON, versions=versions)
+
+    @classmethod
+    def default_version(cls) -> List[str]:
+        return ["3.6", "3.7", "3.8", "3.9"]
+
+
 class RepositoryConfiguration(JsonSerializable):
     """Configuration for ecosystem repository."""
 
     def __init__(
         self,
-        language: str = Languages.PYTHON,
+        language: Optional[LanguageConfiguration] = None,
         dependencies_files: Optional[List[str]] = None,
         extra_dependencies: Optional[List[str]] = None,
         tests_command: Optional[List[str]] = None,
@@ -37,7 +68,7 @@ class RepositoryConfiguration(JsonSerializable):
         """Configuration for ecosystem repository.
 
         Args:
-            language: repository language
+            language: repository language configuration
             dependencies_files: list of dependencies files paths relative to root of repo
                 ex: for python `requirements.txt`
             extra_dependencies: list of extra dependencies for project to install during tests run
@@ -49,7 +80,7 @@ class RepositoryConfiguration(JsonSerializable):
             coverages_check_command: list of commands to run coverage checks
                 ex: for python `coverage3 -m unittest -v && coverage report`
         """
-        self.language = language
+        self.language = language or PythonLanguageConfiguration()
         self.dependencies_files = dependencies_files or []
         self.extra_dependencies = extra_dependencies or []
         self.tests_command = tests_command or []
@@ -65,11 +96,18 @@ class RepositoryConfiguration(JsonSerializable):
     def load(cls, path: str) -> "RepositoryConfiguration":
         """Loads json file into object."""
         with open(path, "r") as json_file:
-            config: RepositoryConfiguration = json.load(
-                json_file, object_hook=lambda d: RepositoryConfiguration(**d)
-            )
-            if config.language == Languages.PYTHON:  # pylint: disable=no-else-return
+            json_data = json.load(json_file)
+            if json_data.get("language"):
+                language = LanguageConfiguration(**json_data.get("language"))
+            else:
+                language = PythonLanguageConfiguration()
+            json_data["language"] = language
+            config: RepositoryConfiguration = RepositoryConfiguration(**json_data)
+            if (  # pylint: disable=no-else-return
+                config.language.name == Languages.PYTHON
+            ):
                 return PythonRepositoryConfiguration(
+                    language=language,
                     dependencies_files=config.dependencies_files,
                     extra_dependencies=config.extra_dependencies,
                     tests_command=config.tests_command,
@@ -90,14 +128,16 @@ class PythonRepositoryConfiguration(RepositoryConfiguration):
 
     def __init__(
         self,
+        language: Optional[LanguageConfiguration] = None,
         dependencies_files: Optional[List[str]] = None,
         extra_dependencies: Optional[List[str]] = None,
         tests_command: Optional[List[str]] = None,
         styles_check_command: Optional[List[str]] = None,
         coverages_check_command: Optional[List[str]] = None,
     ):
+        language = language or PythonLanguageConfiguration()
         super().__init__(
-            language=Languages.PYTHON,
+            language=language,
             dependencies_files=dependencies_files,
             extra_dependencies=extra_dependencies,
             tests_command=tests_command,
@@ -115,6 +155,7 @@ class PythonRepositoryConfiguration(RepositoryConfiguration):
     def default(cls) -> "PythonRepositoryConfiguration":
         """Returns default python repository configuration."""
         return PythonRepositoryConfiguration(
+            language=PythonLanguageConfiguration(),
             dependencies_files=["requirements.txt"],
             extra_dependencies=["pytest", "coverage"],
             tests_command=["pip check", "pytest"],  # -W error::DeprecationWarning


Test patch:
diff --git a/resources/members.json b/resources/members.json
new file mode 100644
index 0000000..e69de29
diff --git a/tests/models/test_configuration.py b/tests/models/test_configuration.py
index ddc2cef..94745d3 100644
--- a/tests/models/test_configuration.py
+++ b/tests/models/test_configuration.py
@@ -1,6 +1,11 @@
 """Tests for configuration files."""
+import os
 
-from ecosystem.models import RepositoryConfiguration, PythonRepositoryConfiguration
+from ecosystem.models import (
+    RepositoryConfiguration,
+    PythonLanguageConfiguration,
+    PythonRepositoryConfiguration,
+)
 from tests.common import TestCaseWithResources
 
 
@@ -10,6 +15,7 @@ class TestRepositoryConfiguration(TestCaseWithResources):
     def test_save_and_load(self):
         """Tests saving and loading of configuration,"""
         config = RepositoryConfiguration(
+            language=PythonLanguageConfiguration(versions=["3.6"]),
             dependencies_files=["requirements.txt", "requirements-dev.txt"],
             extra_dependencies=["qiskit"],
             tests_command=["python -m unittest -v"],
@@ -24,7 +30,8 @@ class TestRepositoryConfiguration(TestCaseWithResources):
 
         recovered_config = RepositoryConfiguration.load(save_path)
 
-        self.assertEqual(config.language, recovered_config.language)
+        self.assertEqual(config.language.name, recovered_config.language.name)
+        self.assertEqual(config.language.versions, recovered_config.language.versions)
         self.assertEqual(config.tests_command, recovered_config.tests_command)
         self.assertEqual(config.dependencies_files, recovered_config.dependencies_files)
         self.assertEqual(config.extra_dependencies, recovered_config.extra_dependencies)
@@ -50,3 +57,20 @@ class TestRepositoryConfiguration(TestCaseWithResources):
             self.assertTrue(command in rendered_tox)
         for command in config.coverages_check_command:
             self.assertTrue(command in rendered_tox)
+
+    def test_non_complete_config_load(self):
+        """Tests non full configuration load."""
+        path_to_config = (
+            f"{os.path.dirname(os.path.abspath(__file__))}/"
+            f"../resources/test_ecosystem_config.json"
+        )
+
+        config = PythonRepositoryConfiguration.load(path_to_config)
+        self.assertEqual(config.language.name, "python")
+        self.assertEqual(
+            config.language.versions, PythonLanguageConfiguration.default_version()
+        )
+        self.assertEqual(config.dependencies_files, [])
+        self.assertEqual(config.tests_command, [])
+        self.assertEqual(config.styles_check_command, [])
+        self.assertEqual(config.extra_dependencies, [])
diff --git a/tests/resources/configured_python_repository/ecosystem.json b/tests/resources/configured_python_repository/ecosystem.json
index dbd0b63..ffce99b 100644
--- a/tests/resources/configured_python_repository/ecosystem.json
+++ b/tests/resources/configured_python_repository/ecosystem.json
@@ -6,7 +6,10 @@
     "extra_dependencies": [
         "pytest"
     ],
-    "language": "python",
+    "language": {
+        "name": "python",
+        "versions": ["3.6"]
+    },
     "tests_command": [
         "pytest"
     ],
diff --git a/tests/resources/test_ecosystem_config.json b/tests/resources/test_ecosystem_config.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/tests/resources/test_ecosystem_config.json
@@ -0,0 +1,1 @@
+{}
\ No newline at end of file