-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove metaclasses #388
Remove metaclasses #388
Conversation
So that it works when column is in tbs.columb_by_name. Deprecate simulations.self.entity_by_column_name Introduce simulations.getVariableEntity
In formulas.add_column_to_tax_benefit_system For temp retrocompatibility reason This function will disappear anyway
Creating a tax benefit system.A tax benefit system is a set of :
Example # This hasn't change: I need to create my entities and scenario.
class Scenario(AbstractScenario):
#...
class Familles(AbstractEntity):
#...
class Individus(AbstractEntity):
#...
# Variable classes can still inherit Variable, DatedVariable, EntityToPersonColumn or PersonToEntityColumn.
class age_en_mois(Variable):
#...
class birth(Variable):
#...
class salaire_brut(Variable):
#...
# I want to define a particular tax benefit system.
class TestTaxBenefitSystem(TaxBenefitSystem):
# The construction of the TBS is done in its constructor.
def __init__(self):
# I first need to call the parent constructor for a basic initialization, providing the entities in a simple list.
entities = [Familles, Individus]
TaxBenefitSystem.__init__(self, entities)
self.Scenario = Scenario
# I add a variable to the TBS
self.add_variable(age_en_mois)
# I add several variables at the same time.
self.add_variables(birth, salaire_brut)
# I add all the ones declared in a python file
self.add_variables_from_file("/path/to/openfisca_variables.py")
# I recursively browse a directory to add all the variables declared in it.
self.add_variables_from_directory("other/path/to/openfisca/variables/")
# I declare the main parameter file
path_to_root_params = 'path/to/root_params.xml'
self.add_legislation_params(path_to_root_params)
# I add another parameter file
path_to_crds_params = 'path/to/crds_params.xml'
# It will be added to the tree into the node `csg.activite`
self.add_legislation_params(path_to_crds_params, 'csg.activite')
# I can then use it
tbs = TestTaxBenefitSystem()
# Will return the appropriate column
tbs.get_column('birth')
# Will compute and return the legislation JSON
tbs.get_legislation()
# Will return the more easily browsable legislation for a given time.
legislation = tbs.get_compact_legislation(Instant((2013, 1, 1)))
montant_al = legislation.al.montant |
7cb2a4e
to
b829b47
Compare
Very nice work ! Congratulations ! |
Creating a reformA reform is a usually small set of modifications applied to a tax benefit systems (i.e. to its parameters or variables), in order to study the impact of legislation modification. The way to build them changes significantly with this PR. Example: # I declare a new variable
class charge_loyer(Variable):
column = columns.FloatCol
entity_class = entities.FoyersFiscaux
label = u"Charge déductible pour paiement d'un loyer"
def function(self, simulation, period):
return period, (...)
# I declare another one aiming at replacing an existing variable
class charges_deduc(Variable):
label = u"Charge déductibles intégrant la charge pour loyer (Trannoy-Wasmer)"
reference = "charges_deduc"
def function(self, simulation, period):
return period, (...)
# I declare a legislation modifier. The API hasn't changed and is still rough to handle.
def modify_legislation_json(reformed_legislation):
reform_legislation_subtree = {
"@type": "Node",
"description": "Charge de loyer",
"children": {...},
}
reformed_legislation['children']['charge_loyer'] = reform_legislation_subtree
return reformed_legislation
# I can finally declare my reform
class trannoy_wasmer(Reform):
name = u'Loyer comme charge déductible (Trannoy-Wasmer)'
# I need to implement this method.
# Its role is to transform the reference tax benefit system into the reformed one.
# When this method is called, self is a copy of the reference tax benefit system.
def apply(self):
# I add a new variable
self.add_variable(charge_loyer)
# I replace an existing one
self.update_variable(charges_deduc)
# I modify the legislation
self.modify_legislation_json(modifier_function = modify_legislation_json)
# I can also neutralize a column
self.neutralize_column('rsa')
# I can then use my reform
tbs = SomeTaxBenefitSystem()
# I can my slightly different tax benefit system
reformed_tbs = trannoy_wasmer(tbs) |
Looks very good, indeed. |
Some code has been removed, as was either already said to be deprecated, or didn't seem to be used:
|
No worries this has big impacts on the API so all questions and feedback are really welcome so that we end up with something satisfying for everyone ;) |
If update_legislation and updated_legislation_items were not replaced I may still use them ... |
Creating an extension:An extension is a set of variables and parameters that can be added to a tax benefit system, but which purpose is too specific to be added to the general shared code base of this tax benefit system (e.g. Paris social prestations in An extension is loaded this way: tbs = SomeTaxBenefitSystem()
tbs.load_extension('path/to/extensions/folder') The extension folder must have a specific architecture:
|
🎉
👍
This should be simplified by YAML params. |
Wow it's a good design, I especially like the There are still |
@@ -185,7 +185,7 @@ def fill_simulation(self, simulation, use_set_input_hooks = True, variables_name | |||
# All parallel axes have the same count and entity. | |||
first_axis = parallel_axes[0] | |||
axis_count = first_axis['count'] | |||
axis_entity = simulation.entity_by_column_name[first_axis['name']] | |||
axis_entity = simulation.getVariableEntity(first_axis['name']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In Python the convention would be camel case: get_variable_entity
:)
Also, very good to get rid of |
import tempfile | ||
import subprocess | ||
temp_dir = tempfile.mkdtemp() | ||
clone_command = ' '.join(['git clone', path, temp_dir, '&> /dev/null']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Waw that's perhaps not the right place :)
I guess the usage is:
tbs.load_extension('https://github.com/xxx/yyy.git')
I'd like to decouple the git clone
from the loading of the extension.
We can do:
$ pip install git+https://github.com/xxx/yyy.git
then
tbs.load_extension_from_package('yyy')
where yyy
is a Python package name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yay I admit this may be a little weird. I thought I would need it, but actually I'm not using it, so I'll just remove the feature. Adding extensions from a package seems interesting too, I'll keep that in mind.
@benjello I don't have a strong opinion about |
@@ -185,7 +185,7 @@ def fill_simulation(self, simulation, use_set_input_hooks = True, variables_name | |||
# All parallel axes have the same count and entity. | |||
first_axis = parallel_axes[0] | |||
axis_count = first_axis['count'] | |||
axis_entity = simulation.getVariableEntity(first_axis['name']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cbenz thanks for the quick fix.
278595f
to
9c03bf1
Compare
This seems GTM to me. @benjello what about |
eec16f4
to
e0b68ad
Compare
e0b68ad
to
e6b58f5
Compare
From a @cbenz commit erased my mistake
e6b58f5
to
2a250c7
Compare
2a250c7
to
0a07fcd
Compare
@@ -29,6 +29,6 @@ previous = true | |||
hang-closing = true | |||
; E128 continuation line under-indented for visual indent | |||
; E251 unexpected spaces around keyword / parameter equals | |||
ignore = E128,E251 | |||
ignore = E128,E251,F405 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a description of F405 to add before ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
F405 name may be undefined, or defined from star imports: module
The initial goal of this PR was to get rid of the metaclass system. As is it pretty deep in the core of openfisca, a lot of other changes comes with it. Feedback and review from other contributors would be particularly appreciated here
Metaclass system
In the former system, openfisca variables were loaded into the tax benefit system statically through metaclasses. Concretely, when python was importing the different modules files, the variables would be automatically loaded in the entities classes. Then only the tax benefit system would be created, and would be able to access these variables through the entity classes.
This is pretty lopsided for many reasons. The lifecyles of the different objects is pretty hard to handle, as many things happen at the loading of the python modules. This make it very hard for reforms to change the tax benefit system (hence the rough API).
This mechanism is removed with this PR. See next comment about the new TaxBenefitSystem API to check the new mechanism.
To do :