0

This probably already looks like a duplicate; here's the scenario:

default_config.py:

unit_id = -1  # a serial number; int. -1 is a test unit, for example

def um():
    return unit_id % 60  # stagger uploads

upload_hour = 2  #am
upload_minute = property( um )  # <- something that works needed here...

config.py

from default_config import *
# Override defaults here, if necessary

unit_id = 12  # ACTUAL serial number...

some_file.py

import config as cfg

do_something(cfg.upload_hour, cfg.upload_minute)

print cfg.upload_minute * 5  # should be an int...?

So, the goals are:

  • A specific config file can override the defaults, which works fine
  • Some values which are calculated can be accessed - after the overrides are applied - but in a "transparent" way (ie. without the prop() brackets)

This seemed to be simple for python properties, but after various combinations, doesn't work. I guess its something to do with the function being defined on a module, not an object, and unbound first variables, etc...

Either I get a "property object" back, and can't then use operators on it, etc, or I can't get the value the property should calculate and return, or after many iterations I can't remember, some other error...

BugSpray
  • 113
  • 9
  • Suggest you [edit] your question and add something explicit that doesn't work. – martineau Aug 16 '16 at 03:44
  • The code as stated will import, etc - but doesn't work as per the stated API goal. I've added a print statement which I would like to work - the property should just be an int by this stage – BugSpray Aug 16 '16 at 03:50

2 Answers2

0

I guess its something to do with the function being defined on a module, not an object...

Why not use an object then? :)

default_config.py

_DEFAULT_UNIT_ID = -1
_DEFAULT_UPLOAD_HOUR = 2
_MINUTES_PER_HOUR = 60


class BaseConfig(object):
    def __init__(self, unit_id=_DEFAULT_UNIT_ID, upload_hour=_DEFAULT_UPLOAD_HOUR):
        self.unit_id = unit_id
        self.upload_hour = upload_hour

    @property
    def upload_minute(self):
        return self.unit_id % _MINUTES_PER_HOUR

config.py

from default_config import BaseConfig

# organized place to put non-default parameters
config_dict = {
    'unit_id': 12,
    'upload_hour': 3,
}

CONFIG = BaseConfig(**config_dict)

some_file.py

from config import CONFIG

print CONFIG.upload_hour, CONFIG.upload_minute  # "3 12"

I would also consider just combining default_config.py and config.py if there's no need to separate them, since it would be easier to see what keyword arguments the BaseConfig takes.

Karin
  • 8,404
  • 25
  • 34
  • This is a good suggestion - thanks for looking at it from a different perspective. Things may go down this path, but I'm going to exhaust all possibilities the current way first. – BugSpray Aug 16 '16 at 03:58
0

You can't define special methods on modules, but entries in sys.modules don't have to be module objects, they can also be class instance objects. This means you can take advantage of their attribute-access special methods like this:

default_config.py

class DefaultConfig(object):
    unit_id = -1  # a serial number
    upload_hour = 2  # am

    @property
    def upload_minute(self):
        return self.unit_id % 60  # stagger uploads

config.py

import sys
from default_config import DefaultConfig

# override defaults
DefaultConfig.unit_id = 12

# see http://stackoverflow.com/questions/5365562/why-is-the-value-of-name-changing-after-assignment-to-sys-modules-name
# as to why the _ref is necessary
_ref, sys.modules[__name__] = sys.modules[__name__], DefaultConfig()

# clean up this module's namespace
del sys, DefaultConfig

some_file.py

from __future__ import print_function
import config as cfg

def do_something(hour, minute):
    print('do_something({}, {}) called'.format(hour, minute))

do_something(cfg.upload_hour, cfg.upload_minute)
print(cfg.upload_minute * 5)

Output from running some_file.py:

do_something(2, 12) called
60
martineau
  • 119,623
  • 25
  • 170
  • 301