I used to work with Flask which offers an easy way to configure the application running in different modes. (dev, test, prod, ...)
class BaseConfig:
MY_PATH = "Something"
class DevelopmentConfig(BaseConfig):
MY_PATH = "Something else"
# ...
I am trying to build something similar but without using Flask. Here is the structure of the most simple code I could find:
-src
- main.py
- zip2h5
- __init__.py
- foo.py
-test
- __init__.py
- test_foo.py
The object Foo.py has a method path which output "path/to/dev" when in dev mode, "path/to/test" when in test mode. Writing if statements in the code would be messy and hard to test properly. Using environment variable seems much better. How and where do I set the configurations that Flask does?
# foo.py
class Foo():
def __init__(self, name):
self.name = name
def path(self):
return "path/in/dev"
# test_foo.py
class TestFoo(unittest.TestCase):
def test_path(self):
boo = Foo("Boo")
expected = "path/in/test"
self.assertEquals(boo.path(), expected)
Please, do not tell me I can patch the method. As I have said, this is just an example.
The environment for your process is available via the os module.
You can simply inject different environment variables for the path in your dev and test cases. I'm not sure how your running your tests, but usually you can do something like PATH='path/in/test' tests.sh to accomplish what you need.
I use the dotenv and keep .env files in my project root to manage this. I have a base test class that loads .env.test instead of .env for testing configuration.
Do it the same was Flask does it. Have multiple Config classes, then pass env as a parameter e.g
class Foo():
def __init__(self, name, env):
self.name = name
self.env = env
def path(self):
if self.env == 'TEST':
#initialize TestConfig class here
return TestConfigPath
test_foo.py
class TestFoo(unittest.TestCase):
def test_path(self):
boo = Foo("Boo")
expected = "path/in/test"
self.assertEquals(boo.path(), expected)
Related
I am familiar with Pycharm and new to VSCode. I would like to "go to definition" like I did in Pycharm but I can't figure out how to. Btw I set up the environment in WSL2 Ubuntu and code from local with WSL VSCode extension.
Directory tree view is like :
src
|-calculation
|-base_class.py
|-particular_class.py
|-db_related_module
|-db_class.py
and each module has:
base_class.py
class BaseClass():
def __init__(self, DbClass):
self.db_cls = DbClass
particular_class.py
from base_class import BaseClass
from db_related_module.db_class import DbClass
class ParticularCalClass(BaseClass):
def __init__(self):
super().__init__(
DbClass
)
def some_calculation(self):
do_db_stuff = self.db_cls
ret = do_db_stuff.execute_sql()
return ret
def main():
calc_cls = ParticularCalClass()
print(calc_cls.some_calculation())
main()
db_class.py
class DbClass():
def __init__(self):
pass
def execute_sql():
return "execute some sql"
Here I would like to jump from particular_class.py
ret = do_db_stuff.execute_sql()
to db_class.py
def execute_sql():
as it is the definition.
After testing this is the same problem on my machine. So I filed a report on GitHub and here is their response:
The import in line from base_class import BaseClass is incorrect in this scenario. It should be from calculation.base_class import BaseClass. And you will need to have PYTHONPATH=./src. If you have from base_class import BaseClass python won't know where the base_class module is, so you won't see go to definition working. you will wither have to add calculation directory to sys.path or change the import as I recommended above.
Normally that would be using F12, or right click and go to definition.
I'm trying to write a class with a method that behaves differently based on data given on the initialization of the object. I want to pass this object code to run stored in a different file. The behavior should be as follows
foo = Myclass(config="return_a.py")
bar = Myclass(config="return_b.py")
foo.baz() # returns a
bar.baz() # returns b
# where return_a.py and return_b.py are files in the directory
The closest I've come to fixing it so far is using exec and having my configured python write to a file which I then read from. I don't know how I'd do this in memory
You can use importlib to import the files dynamically.
Let's say your project has the structure:
.
├── main.py
├── return_a.py
└── return_b.py
you can put in main.py your code:
import importlib
class Myclass:
def __init__(self, config) -> None:
config = importlib.import_module(
config)
self.baz = config.baz
foo = Myclass(config="return_a")
bar = Myclass(config="return_b")
foo.baz()
bar.baz()
This is assuming you have the function baz your return_a and return_b files. For example:
#return_a.py
def baz():
print("I am A")
#return_b.py
def baz():
print("I am B")
Now if you execute main.py you will get:
I am A
I am B
I have 3 scripts: params.py (it defines a configuration class), foo.py (it uses that configuration) and main.py (it initializes the configuration and calls foo).
params.py:
class Config:
def __init__(self, x=0):
self.val = x
global config
config = Config()
foo.py:
from params import config
def foo():
return config.val + 5
main.py:
from params import config
from foo import foo
config = Config(10)
print(foo())
But instead of print 15, it prints 5. How can I fix it? It occurs because when foo.py does the import, it initializes config with 0. But, what can I do to modify from main the config value and read the new value from all other scripts?
Thank you!
Conceptually, you need to separate an object like Config() from the variables that may be referencing it at any given time. When params.py does config = Config(), it creates a Config object and assigns it to a variable in the params module namespace. It is params.config.
When main.py does from params import config, it adds a reference to this Config object to its own namespace. Now there are two references to the same object, one in params.config and another in main.config. So far, so good. from X import Y adds a binding to X.Y into the current namespace. Since params.config is a mutable class instance, main could change the values in that single Config object and it would be seen by all other referrers to that same object. config.val = 10 would be seen by all.
Now things go off the rails. When main does config = Config(10), it creates a new Config object and reassigns that variable to the main namespace. Now params.config references the first object and main references the second. That means changes made to the second object are not seen by the first.
If you want everyone to see the same object, you need to keep the namespace qualification. The scripts would change to
foo.py:
import params
def foo():
return params.config.val + 5
main.py:
import params
from foo import foo
params.config = Config(10)
print(foo())
Now, all of the scripts are using the one variable params.config and see any changes made to that object. This is kindof fragile as you've seen. If anybody does from params import config, reassiging params.config doesn't work.
global only marks a name in a local scope as being global; it has no affect in a global scope, in that it is already global.
What you want isn't really possible, as global namespaces are specific to an individual module, not the process as a whole.
If the value is defined in params.py, you will need to access it via params from all other modules, include the __main__ module created by your script.
params.py:
class Config:
def __init__(self, x=0):
self.val = x
config = Config()
foo.py:
import params
def foo():
return params.config.val + 5
main.py:
import params
from foo import foo
params.config = params.Config(10)
print(foo())
If you simply modified the existing configuration, you could use
params.py (same as above):
class Config:
def __init__(self, x=0):
self.val = x
config = Config()
foo.py (same as your original foo.py):
from params import config
def foo():
return config.val + 5
main.py
from params import config
from foo import foo
config.val = 10
print(foo())
In general, I don't think this is a good idea, as you're essentially creating a global state that can change from any file that imports the configuration file. This is known as action at a distance.
The best answer is to avoid this pattern altogether. For example, come up with a way to use the configuration file in a read-only manner.
That being said, if you really want to do this, make the variable class-level rather than instance-level, so that there exists only one val shared across the entire program.
class Config:
val = 0
def __init__(self, x=0):
Config.val = x
global config
config = Config()
Then, running main.py will print 15.
I am somewhat of a beginner in python, i am currently writing a suite of test cases with selenium webdriver using unittest; i have also found a lot of useful answers here, but it's time a ask my first question, i have struggled a lot with this and cannot find a proper answer, so any help is greatly appreciated:
For short, i have a suite of multiple tests cases, and in each case the first step is always ".get('Some URL')"; i have written these test cases for a single environment, but i would like to be able to select the URL on which all tests will be executed. In the example below i called the "access_url" method with a specific environment, but i need to do this for all of my scenarios at once, is it possible to do this from where i execute the .py file (e.g. "python example.py")? or to pass it in the .run() method when i select what suite to run?
import HTMLTestRunner
from selenium import webdriver
import unittest
This is a custom class used to create the 'access_url' method
def MyClass(object):
def __init__(self, driver):
self.driver = driver
def access_url(self, URL):
if URL == 'environment 1':
self.driver.get('https://www.google.com/')
elif URL == 'environment 2':
self.driver.get('https://example.com/')
In the classes i use to write test cases the first step is always 'access URL'
class TestScenario01(unittest.TestCase):
def setUp(self):
[...]
def test_01_access(self):
MyClass(self.driver).access_url(URL='environment 2')
def test_02(self):
[...]
def test_03(self):
[...]
In order to run the tests i place them all in a suite and use .run() on them
tc_scenario01 = unittest.TestLoader().loadTestsFromTestCase(TestScenario01)
test_suite = unittest.TestSuite([tc_scenario01])
HTMLReporterCustom.HTMLTestRunner().run(test_suite)
Finally, in order to execute the script i type the follwoing line in CMD: 'python example_file.py
As i mentioned above, all i want to do is to be able to somehow pass the URL one time to all test cases that call the "access_url()" method. Thanks!
You can maintain environment properties in separate config file.
config.py
DEFAULT_ENVIRONMENT='environment1'
URL = {
'environment1': 'https://www.google.com/',
'environment2': 'https://example.com/'
}
Your Class,
from package import config
def MyClass(object):
def __init__(self, driver):
self.driver = driver
def access_url(self):
self.driver.get(config.URL[config.DEFAULT_ENVIRONMENT])
Then test class will be as expected,
class TestScenario01(unittest.TestCase):
def setUp(self):
[...]
def test_01_access(self):
MyClass(self.driver).access_url()
def test_02(self):
[...]
def test_03(self):
[...]
While running test you can change,
main.py
from package import config
config.DEFAULT_ENVIRONMENT = 'enviroment2'
tc_scenario01 = unittest.TestLoader().loadTestsFromTestCase(TestScenario01)
test_suite = unittest.TestSuite([tc_scenario01])
HTMLReporterCustom.HTMLTestRunner().run(test_suite)
You can also pass the environment name while running python main.py.
main.py
if __name__ == '__main__':
config.DEFAULT_ENVIRONMENT = sys.argv[1] if len(sys.argv) > 2 else 'dev'
tc_scenario01 = unittest.TestLoader().loadTestsFromTestCase(TestScenario01)
test_suite = unittest.TestSuite([tc_scenario01])
HTMLReporterCustom.HTMLTestRunner().run(test_suite)
If I have two files helper.app and main.app, I want to be able to accomplish something like this.
helper.py
def configurestuff(dblocationstring):
# Stuff that sets name and location
generic_connection_variable = connectto(dblocationstring)
def dostuff():
# does stuff with the generic_connection_variable
In my main.py, I want to be able to do something like
import helper
helper.configure("customlocationofdb")
helper.dostuff()
#or even
helper.generic_connection_variable.someApplicableMethod()
My goal is so that I can have a main.app that is able to use the "helper" passing variables to setup a connection and reuse that variable if possible within main.app after importing the helper. What is the best way to organize my code to accomplish this? (im not sure how to access generic_connection_variable in my main.py as it is in a function, or what the best way to do this is)
Implementing this as a class allows for greater flexibility:
class Config(object):
DB_STRING = 'some default value'
ANOTHER_SETTING = 'another default'
DEBUG = True
def dostuff(self):
print 'I did stuff to ',self.DEBUG
class ProductionConfig(Config):
DEBUG = False # only turn of debugging
class DevelopmentConfig(Config):
DB_STRING = 'localhost'
def dostuff(self):
print 'Warning! Development system ',self.DEBUG
Store this in any file for example, settings.py. In your code:
from settings import Config as settings
# or from settings import ProductionConfig as settings
print settings.DEBUG # for example
You can define generic_connection_variable to be a module level variable.
So in your helper.py you will have to
generic_connection_variable = None # or whatever default you want.
def configurestuff(dblocationstring):
global generic_connection_variable
# Stuff that sets name and location
generic_connection_variable = connectto(dblocationstring)
def dostuff():
global generic_connection_variable
# does stuff with the generic_connection_variable
It's a bit hard to tell what you are asking, but have you tried making generic_connection_variable an instance variable of helper? (with the self keyword)
# helper.py:
def configurestuff(dblocationstring):
# Stuff that sets name and location
self.generic_connection_variable = connectto(dblocationstring)
Now that generic_connection_variable belongs to an instance of helper instead of being local-scoped to configurestuff, you will be able to use it in main as follows:
import helper
helper.configure("customlocationofdb")
helper.generic_connection_variable.someApplicableMethod()
But you probably need to define a class for generic_connection_variable so it has a method called someApplicableMethod().