diff --git a/README.md b/README.md index 474729f..d70799f 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,56 @@ There is a special class representing parent-children relation (one-to-many). It the relation. For example, when OrbitalStation instance changes its attribute `controlling_faction`, the attribute `stations` on the FactionBranch instance is also updated, and vice versa. The parent is kept in sync with the children. +# Can I use the classes without external data source? +From now on - Yes! The whole adapter engine has been extracted, so now, apart from full auto-refreshed classes available +in the `classes` module, you can use the "offline" Model classes available in the `models` module. + +## Model classes +They represent objects (factions, systems, faciton branches and stations) and keep the relation between them, but they +are static objects, not connected to any external data source. If you use these classes, they will only store the data +you have filled them with. For example. creating a SystemModel of system Sol will no longer let you fetch data about +"Sol" system: + +```python +from edclasses.models import SystemModel + +sol_model = SystemModel.create(name="Sol") +# the model has the same attributes ed class has, however it will not look for data anywhere else: + +print(sol_model.stations) +[] +# A normal instance of System class would at this point query API Adapter for data, but the model doesn't do that. +``` + +### Advantages +Is there anything special about the model class itself if it doesn't fetch the data? Well, yes - relations. The model +classes are meant to work like a database - keep links between different objects in your program. Look at this example: + +```python +>>> from edclasses.models import SystemModel, FactionModel, FactionBranchModel + +>>> sol = SystemModel.create(name="Sol") +>>> sol.faction_branches +[] + +>>> faction = FactionModel.create(name="Some Faction") +>>> faction.faction_branches +[] + +>>> faction_branch = FactionBranchModel.create(faction=faction, system=sol) +# at these point the relations between are updated: + +>>> sol.faction_branches +[Some Faction in Sol] + +>>> faction.faction_branches +[Some Faction in Sol] +``` + +How can this be useful? Even though `edlasses` have been designed to be used with the API, maybe you have some idea +to create a whole universe simulation based on a CSV file on some Database of your own? This objects should make it +possible to do so without writing your own classes. + # Where is the data coming from? The library comes with a simple adapter to a magnificent API at www.elitebgs.app (seriously, they made a great job, you should check it out if you haven't already). @@ -130,5 +180,7 @@ factions of interests. If your app is too data-hungry, it will result in the fol Yes, the provided EliteBgsApiAdapter is just an example. You can write a similar adapter, or extend this one, and then connect it to the proper class in edclasses. I will add a more detailed tutorial in the future. +You can also use the model classes (check out ## model classes above) and create your own data source system. + # Why don't you XYZ: The project is in very early phase. Have any idea? Please open an issue, I would be glad to discuss. diff --git a/pyproject.toml b/pyproject.toml index b673501..c49f07f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "elite-dangerous-classes-library" -version = "0.0.6" +version = "0.1.0" description = "Library representing various objects in Elite Dangerous" readme = "README.md" authors = [{ name = "Michał Karaś", email = "mjkaras93@gmail.com" }] diff --git a/src/edclasses/__init__.py b/src/edclasses/__init__.py index c798808..793ec04 100644 --- a/src/edclasses/__init__.py +++ b/src/edclasses/__init__.py @@ -1 +1 @@ -from .classes import * +from .classes import System, OrbitalStation, Faction, FactionBranch diff --git a/src/edclasses/api_adapters/utils.py b/src/edclasses/api_adapters/utils.py index 3ff94e4..e1635af 100644 --- a/src/edclasses/api_adapters/utils.py +++ b/src/edclasses/api_adapters/utils.py @@ -1,6 +1,6 @@ from decimal import Decimal -import edclasses +# TODO: Fix ugly imports def get_orbital_station( @@ -11,14 +11,15 @@ def get_orbital_station( services=None, controlling_faction=None, ): + from edclasses import System, OrbitalStation if isinstance(system, str): - system = edclasses.System.create(name=system) + system = System.create(name=system) if distance_to_arrival and not isinstance(distance_to_arrival, Decimal): distance_to_arrival = Decimal(distance_to_arrival) - return edclasses.OrbitalStation.create( + return OrbitalStation.create( name=name, station_type=station_type, system=system, @@ -29,18 +30,24 @@ def get_orbital_station( def get_faction(name): - return edclasses.Faction.create(name=name) + from edclasses import Faction + + return Faction.create(name=name) def get_faction_branch(faction, system): + from edclasses import System, Faction, FactionBranch + if isinstance(system, str): - system = edclasses.System.create(name=system) + system = System.create(name=system) if isinstance(faction, str): - faction = edclasses.Faction.create(name=faction) + faction = Faction.create(name=faction) - return edclasses.FactionBranch.create(faction=faction, system=system) + return FactionBranch.create(faction=faction, system=system) def get_system(name): - return edclasses.System.create(name=name) + from edclasses import System + + return System.create(name=name) diff --git a/src/edclasses/classes.py b/src/edclasses/classes.py index 1b4327a..e9c93c2 100644 --- a/src/edclasses/classes.py +++ b/src/edclasses/classes.py @@ -1,253 +1,39 @@ -from decimal import Decimal -from typing import Optional, List - -import edclasses.api_adapters.elite_bgs_adapter as bgs_adapter -from . import enums -from .utils import UniqueInstanceMixin, OneToManyRelation, AutoRefreshMixin - - -EXPIRATION_TIME_MINUTES = 20 - - -class System(UniqueInstanceMixin, AutoRefreshMixin): - keys = ("name",) - registry = {} - adapter = bgs_adapter.EliteBgsSystemAdapter() +from .api_adapters.elite_bgs_adapter import ( + EliteBgsSystemAdapter, + EliteBgsFactionAdapter, + EliteBgsFactionBranchAdapter, + EliteBgsStationAdapter, +) +from .models import SystemModel, FactionModel, FactionBranchModel, OrbitalStationModel +from .utils import AutoRefreshMixin + + +class System(AutoRefreshMixin, SystemModel): + adapter = EliteBgsSystemAdapter() refreshed_fields = ( "faction_branches", "stations", "eddb_id", ) - EXPIRATION_TIME_MINUTES = EXPIRATION_TIME_MINUTES - - _stations_relation = OneToManyRelation.create( - parent_class_name="System", - child_class_name="OrbitalStation", - ) - _faction_branches_relation = OneToManyRelation.create( - parent_class_name="System", - child_class_name="FactionBranch", - ) - - def __init__( - self, - name: str, - stations=None, - faction_branches=None, - eddb_id=None, - **kwargs, - ): - self.eddb_id = eddb_id - self.name = name - self.stations = stations or [] - self.faction_branches = faction_branches or [] - self._set_adapter(**kwargs) - super().__init__() - - def __repr__(self): - return f"System '{self.name}'" - - def _stations_setter(self, value): - self._stations_relation.set_for_parent(self, value) - - def _stations_getter(self): - return self._stations_relation.get_for_parent(self) - - stations = property( - fget=_stations_getter, - fset=_stations_setter, - ) - - def _faction_branches_setter(self, value): - self._faction_branches_relation.set_for_parent(self, value) - - def _faction_branches_getter(self): - return self._faction_branches_relation.get_for_parent(self) - - faction_branches = property( - fget=_faction_branches_getter, fset=_faction_branches_setter - ) -class Faction(UniqueInstanceMixin, AutoRefreshMixin): - keys = ("name",) - registry = {} - adapter = bgs_adapter.EliteBgsFactionAdapter() +class Faction(AutoRefreshMixin, FactionModel): + adapter = EliteBgsFactionAdapter() refreshed_fields = ("faction_branches",) - EXPIRATION_TIME_MINUTES = EXPIRATION_TIME_MINUTES - - _faction_branches_relation = OneToManyRelation.create( - parent_class_name="Faction", - child_class_name="FactionBranch", - ) - - def __init__( - self, - name: str, - **kwargs, - ): - self.name = name - self._set_adapter(**kwargs) - super().__init__() - - def __repr__(self): - return f"Faction '{self.name}'" - - def _faction_branches_setter(self, value): - self._faction_branches_relation.set_for_parent(self, value) - - def _faction_branches_getter(self): - return self._faction_branches_relation.get_for_parent(self) - - faction_branches = property( - fget=_faction_branches_getter, - fset=_faction_branches_setter, - ) - - -class FactionBranch(UniqueInstanceMixin, AutoRefreshMixin): - keys = ( - "faction", - "system", - ) - registry = {} - adapter = bgs_adapter.EliteBgsFactionBranchAdapter() - refreshed_fields = ("influence", "stations") - EXPIRATION_TIME_MINUTES = EXPIRATION_TIME_MINUTES - - _system_relation = OneToManyRelation.create( - parent_class_name="System", - child_class_name="FactionBranch", - ) - _faction_relation = OneToManyRelation.create( - parent_class_name="Faction", - child_class_name="FactionBranch", - ) - _stations_relation = OneToManyRelation.create( - parent_class_name="FactionBranch", - child_class_name="OrbitalStation", - ) - - def __init__( - self, - faction: Faction, - system: System, - is_main: bool = False, - influence: Decimal = None, - stations: List = None, - **kwargs, - ): - self.faction = faction - self.system = system - self.is_main = is_main - self.influence = influence - self.stations = stations or [] - self._set_adapter(**kwargs) - super().__init__() - def __repr__(self): - return f"{self.faction} in {self.system}" - - def _system_setter(self, value): - self._system_relation.set_for_child(self, value) - - def _system_getter(self): - return self._system_relation.get_for_child(self) - - system = property( - fget=_system_getter, - fset=_system_setter, - ) - def _faction_setter(self, value): - self._faction_relation.set_for_child(self, value) - - def _faction_getter(self): - return self._faction_relation.get_for_child(self) - - faction = property( - fget=_faction_getter, - fset=_faction_setter, - ) - - def _stations_setter(self, value): - self._stations_relation.set_for_parent(self, value) - - def _stations_getter(self): - return self._stations_relation.get_for_parent(self) - - stations = property( - fget=_stations_getter, - fset=_stations_setter, +class FactionBranch(AutoRefreshMixin, FactionBranchModel): + adapter = EliteBgsFactionBranchAdapter() + refreshed_fields = ( + "influence", + "stations", ) -class OrbitalStation(UniqueInstanceMixin, AutoRefreshMixin): - keys = ( - "name", - "system", - ) - registry = {} - adapter = bgs_adapter.EliteBgsStationAdapter() +class OrbitalStation(AutoRefreshMixin, OrbitalStationModel): + adapter = EliteBgsStationAdapter() refreshed_fields = ( "controlling_faction", "distance_to_arrival", "services", ) - EXPIRATION_TIME_MINUTES = EXPIRATION_TIME_MINUTES - - _system_relation = OneToManyRelation.create( - parent_class_name="System", - child_class_name="OrbitalStation", - ) - _controlling_faction_relation = OneToManyRelation.create( - parent_class_name="FactionBranch", - child_class_name="OrbitalStation", - ) - - def __init__( - self, - name: str, - station_type: enums.StationType, - system: System, - distance_to_arrival: Optional[Decimal] = None, - services: Optional[List] = None, - controlling_faction=None, - **kwargs, - ): - self.name = name - self.station_type = station_type.value - self.system = system - self.distance_to_arrival = distance_to_arrival - self.services = services or [] - self.controlling_faction = None or controlling_faction - self._set_adapter(**kwargs) - super().__init__() - - def __repr__(self): - return f"{self.station_type.title()} '{self.name}'" - - def _system_setter(self, value): - self._system_relation.set_for_child(self, value) - - def _system_getter(self): - return self._system_relation.get_for_child(self) - - system = property( - fget=_system_getter, - fset=_system_setter, - ) - - def _controlling_faction_setter(self, value): - self._controlling_faction_relation.set_for_child( - self, - value, - ) - - def _controlling_faction_getter(self): - return self._controlling_faction_relation.get_for_child(self) - - controlling_faction = property( - fget=_controlling_faction_getter, - fset=_controlling_faction_setter, - ) diff --git a/src/edclasses/models.py b/src/edclasses/models.py new file mode 100644 index 0000000..e4fedb0 --- /dev/null +++ b/src/edclasses/models.py @@ -0,0 +1,223 @@ +from decimal import Decimal +from typing import List, Optional + +from . import enums +from .utils import UniqueInstanceMixin, OneToManyRelation + + +class SystemModel(UniqueInstanceMixin): + keys = ("name",) + registry = {} + _stations_relation = OneToManyRelation.create( + parent_class_name="System", + child_class_name="OrbitalStation", + ) + _faction_branches_relation = OneToManyRelation.create( + parent_class_name="System", + child_class_name="FactionBranch", + ) + + def __init__( + self, + name: str, + stations=None, + faction_branches=None, + eddb_id=None, + **kwargs, + ): + self.eddb_id = eddb_id + self.name = name + self.stations = stations or [] + self.faction_branches = faction_branches or [] + super().__init__() + + def __repr__(self): + return f"System '{self.name}'" + + def _stations_setter(self, value): + self._stations_relation.set_for_parent(self, value) + + def _stations_getter(self): + return self._stations_relation.get_for_parent(self) + + stations = property( + fget=_stations_getter, + fset=_stations_setter, + ) + + def _faction_branches_setter(self, value): + self._faction_branches_relation.set_for_parent(self, value) + + def _faction_branches_getter(self): + return self._faction_branches_relation.get_for_parent(self) + + faction_branches = property( + fget=_faction_branches_getter, fset=_faction_branches_setter + ) + + +class FactionModel(UniqueInstanceMixin): + keys = ("name",) + registry = {} + + _faction_branches_relation = OneToManyRelation.create( + parent_class_name="Faction", + child_class_name="FactionBranch", + ) + + def __init__( + self, + name: str, + **kwargs, + ): + self.name = name + super().__init__() + + def __repr__(self): + return f"Faction '{self.name}'" + + def _faction_branches_setter(self, value): + self._faction_branches_relation.set_for_parent(self, value) + + def _faction_branches_getter(self): + return self._faction_branches_relation.get_for_parent(self) + + faction_branches = property( + fget=_faction_branches_getter, + fset=_faction_branches_setter, + ) + + +class FactionBranchModel(UniqueInstanceMixin): + keys = ( + "faction", + "system", + ) + registry = {} + + _system_relation = OneToManyRelation.create( + parent_class_name="System", + child_class_name="FactionBranch", + ) + _faction_relation = OneToManyRelation.create( + parent_class_name="Faction", + child_class_name="FactionBranch", + ) + _stations_relation = OneToManyRelation.create( + parent_class_name="FactionBranch", + child_class_name="OrbitalStation", + ) + + def __init__( + self, + faction: FactionModel, + system: SystemModel, + is_main: bool = False, + influence: Decimal = None, + stations: List = None, + **kwargs, + ): + self.faction = faction + self.system = system + self.is_main = is_main + self.influence = influence + self.stations = stations or [] + super().__init__() + + def __repr__(self): + return f"{self.faction} in {self.system}" + + def _system_setter(self, value): + self._system_relation.set_for_child(self, value) + + def _system_getter(self): + return self._system_relation.get_for_child(self) + + system = property( + fget=_system_getter, + fset=_system_setter, + ) + + def _faction_setter(self, value): + self._faction_relation.set_for_child(self, value) + + def _faction_getter(self): + return self._faction_relation.get_for_child(self) + + faction = property( + fget=_faction_getter, + fset=_faction_setter, + ) + + def _stations_setter(self, value): + self._stations_relation.set_for_parent(self, value) + + def _stations_getter(self): + return self._stations_relation.get_for_parent(self) + + stations = property( + fget=_stations_getter, + fset=_stations_setter, + ) + + +class OrbitalStationModel(UniqueInstanceMixin): + keys = ( + "name", + "system", + ) + registry = {} + _system_relation = OneToManyRelation.create( + parent_class_name="System", + child_class_name="OrbitalStation", + ) + _controlling_faction_relation = OneToManyRelation.create( + parent_class_name="FactionBranch", + child_class_name="OrbitalStation", + ) + + def __init__( + self, + name: str, + station_type: enums.StationType, + system: SystemModel, + distance_to_arrival: Optional[Decimal] = None, + services: Optional[List] = None, + controlling_faction=None, + **kwargs, + ): + self.name = name + self.station_type = station_type.value + self.system = system + self.distance_to_arrival = distance_to_arrival + self.services = services or [] + self.controlling_faction = None or controlling_faction + super().__init__() + + def __repr__(self): + return f"{self.station_type.title()} '{self.name}'" + + def _system_setter(self, value): + self._system_relation.set_for_child(self, value) + + def _system_getter(self): + return self._system_relation.get_for_child(self) + + system = property( + fget=_system_getter, + fset=_system_setter, + ) + + def _controlling_faction_setter(self, value): + self._controlling_faction_relation.set_for_child( + self, + value, + ) + + def _controlling_faction_getter(self): + return self._controlling_faction_relation.get_for_child(self) + + controlling_faction = property( + fget=_controlling_faction_getter, + fset=_controlling_faction_setter, + ) diff --git a/src/edclasses/tests/factories/classes_factories.py b/src/edclasses/tests/factories/classes_factories.py index 6227147..23b9d6d 100644 --- a/src/edclasses/tests/factories/classes_factories.py +++ b/src/edclasses/tests/factories/classes_factories.py @@ -1,33 +1,26 @@ import factory -from ..mocked_adapter import MockedAdapter -from ...classes import System, Faction, FactionBranch, OrbitalStation +from ...models import SystemModel, FactionModel, FactionBranchModel, OrbitalStationModel from ...enums import StationType -MOCKED_ADAPTER = MockedAdapter() - -class EDModelFactoryBase(factory.Factory): - adapter = MOCKED_ADAPTER - - -class SystemFactory(EDModelFactoryBase): +class SystemFactory(factory.Factory): class Meta: - model = System + model = SystemModel name = factory.Sequence(lambda n: f"System {n}") -class FactionFactory(EDModelFactoryBase): +class FactionFactory(factory.Factory): class Meta: - model = Faction + model = FactionModel name = factory.Sequence(lambda n: f"Faction {n}") -class FactionBranchFactory(EDModelFactoryBase): +class FactionBranchFactory(factory.Factory): class Meta: - model = FactionBranch + model = FactionBranchModel faction = factory.SubFactory(FactionFactory) system = factory.SubFactory(SystemFactory) @@ -36,9 +29,9 @@ class Meta: influence = 50 -class OrbitalStationFactory(EDModelFactoryBase): +class OrbitalStationFactory(factory.Factory): class Meta: - model = OrbitalStation + model = OrbitalStationModel name = factory.Sequence(lambda n: f"Orbital Station {n}") station_type = StationType.STATION diff --git a/src/edclasses/tests/test_classes.py b/src/edclasses/tests/test_classes.py index dde3926..4686235 100644 --- a/src/edclasses/tests/test_classes.py +++ b/src/edclasses/tests/test_classes.py @@ -1,4 +1,11 @@ -from .factories.classes_factories import * +from .. import enums +from ..models import OrbitalStationModel, SystemModel, FactionBranchModel +from ..tests.factories.classes_factories import ( + SystemFactory, + OrbitalStationFactory, + FactionBranchFactory, + FactionFactory, +) class TestClassesRelations: @@ -14,6 +21,14 @@ def test_station_system_relation(self): assert station.system == system assert system.stations == [station] + def test_station_factory(self): + station = OrbitalStationFactory() + + assert isinstance(station.system, SystemModel) + assert isinstance(station.controlling_faction, FactionBranchModel) + assert station.distance_to_arrival == 100 + assert station.services == [] + def test_station_system_relation_when_station_is_created_with_ready_system(self): system = SystemFactory() station = OrbitalStationFactory(system=system) @@ -23,12 +38,11 @@ def test_station_system_relation_when_station_is_created_with_ready_system(self) assert system.stations == [station, station2] def test_create_assigns_station_to_system_correctly(self): - system = System.create(name="Test System", adapter=MOCKED_ADAPTER) - station = OrbitalStation.create( + system = SystemModel.create(name="Test System") + station = OrbitalStationModel.create( system=system, name="Super Station", - station_type=StationType.STATION, - adapter=MOCKED_ADAPTER, + station_type=enums.StationType.STATION, ) assert station.system == system diff --git a/src/edclasses/tests/test_relations.py b/src/edclasses/tests/test_relations.py index d5ff1d7..a02daaf 100644 --- a/src/edclasses/tests/test_relations.py +++ b/src/edclasses/tests/test_relations.py @@ -1,4 +1,4 @@ -from edclasses import OneToManyRelation +from ..utils import OneToManyRelation class Parent: diff --git a/src/edclasses/utils.py b/src/edclasses/utils.py index dc55511..6c4b782 100644 --- a/src/edclasses/utils.py +++ b/src/edclasses/utils.py @@ -1,6 +1,6 @@ import datetime from collections import defaultdict -from typing import Tuple, List +from typing import List def return_first_match(func, items): @@ -145,7 +145,11 @@ class InstanceAlreadyExists(Exception): class AutoRefreshMixin: refreshed_fields = tuple() adapter = None - EXPIRATION_TIME_MINUTES = 60 + EXPIRATION_TIME_MINUTES = 15 + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._set_adapter(**kwargs) def _set_adapter(self, **kwargs): self.adapter = kwargs.get("adapter", self.adapter)