python-sphinx extension, how to get name of current object? - python

How to get the name of parent object in Python code for which is current documentation build for? I mean how to get name of class "ExampleCls0" in MyDirective.run()?
class ExampleCls0():
"""
.. mydirect::
"""
Lets suppose that we have Spring directive called mydirect.
And it is correctly registered in Sphinx and documentation is build for python code.
class MyDirective(Directive):
required_arguments = 0
optional_arguments = 0
has_content = True
option_spec = {}
def run(self):
env = self.state.document.settings.env
def setup(app):
app.add_directive('mydirect', MyDirective)
For build I am using:
from sphinx.cmdline import main as sphinx_main
from sphinx.ext.apidoc import main as apidoc_main
apidoc_main(["--module-first", "--force", "--full",
"--output-dir", "doc/", "."])
sphinx_main(["-b", "html", "-E",
"-c", pwd,
"doc/",
"doc_build/",
])

I do not know if name of the parent object can be accessed somewhere in Directive.run method, but I found out that it is possible to read the name later.
class SchematicLink(nodes.TextElement):
#staticmethod
def depart_html(self, node):
self.depart_admonition(node)
#staticmethod
def visit_html(self, node):
parentClsNode = node.parent.parent
assert parentClsNode.attributes['objtype'] == 'class'
assert parentClsNode.attributes['domain'] == 'py'
sign = node.parent.parent.children[0]
assert isinstance(sign, desc_signature)
absoluteName = sign.attributes['ids'][0]
print(absoluteName) # file0.ExampleCls0
self.visit_admonition(node)
class MyDirective(Directive):
required_arguments = 0
optional_arguments = 0
def run(self):
schema_node = SchematicLink()
self.state.nested_parse(self.content,
self.content_offset,
schema_node)
return [schema_node]
def setup(app):
app.add_node(SchematicLink,
html=(SchematicLink.visit_html,
SchematicLink.depart_html))
app.add_directive('mydirect', MyDirective)
And this is probably good example how NOT to do it. Code reads id from label of class doc.

Related

How to update an instance variable when another variable is changed?

I have the following class and I want the instance variable api_id_bytes to update.
class ExampleClass:
def __init__(self):
self.api_key = ""
self.api_id = ""
self.api_id_bytes = self.api_key.encode('utf-8')
I'd like to be able to have this outcome:
>>>conn = ExampleClass()
>>>conn.api_key = "123"
>>>conn.api_id = "abc"
>>>print(conn.api_id_bytes)
b'123'
>>>
I basically need the self.api_key.encode('utf-8') to run when an api_id is entered but it doesn't, it only does through the initial conn = ExampleClass().
I'm not sure what this is called so searching didn't find an answer.
Here's how you could do it by making api_id_bytes a property.
class ExampleClass:
def __init__(self):
self.api_key = ""
self.api_id = ""
#property
def api_id_bytes(self):
return self.api_key.encode('utf-8')
Now conn.api_id_bytes will always be correct for the current value of conn.api_key.

How to properly mock private members of a class

I am trying to write some unit tests for a method that depends on another private method. - As shown in the example below:
def is_member_of(self, group_name):
members = self.__get_group_members(group_name)
The private method that I'd like to mock is __get_group_members; I'd also like to mock the private attribute __user_id since it will be used in the is_member_of function (not shown in the example above).
What I have so far:
import unittest
from unittest import mock
class Test(unittest.TestCase):
group_data = []
user_id = 'test_user_id'
def mock_dependencies(self, x):
x.__user_id = mock.PropertyMock(return_value=self.user_id)
x.__get_group_members = mock.MagicMock(return_value=self.group_data)
def first_test(self):
x = A(('name', 'group'))
self.mock_dependencies(x)
x.is_member_of('test_group')
When I invoke x.is_member_of() the mocking doesn't work as anticipated.
You can access a private attribute in Python since private and protected are by convention. - What you're looking for is basically using _ClassName__private_attribute_name since, python carries out the renaming in order to achieve the convention agreed upon.
Example (returning a MagicMock):
with mock.patch.object(Class, '_ClassName__private_attribute_name', return_value='value') as obj_mock:
pass
Example (returning a raw value):
with mock.patch.object(Class, '_ClassName__private_attribute_name', new_callable=PropertyMock) as obj_mock:
obj_mock.return_value = 'string value'
Class is a reference to the class itself - not the instance.
Complete Example:
from unittest.mock import patch, PropertyMock
from unittest import TestCase, main
class Private:
__attr = 'hello'
class PrivateTest(TestCase):
#patch.object(Private, '_Private__attr', new_callable=PropertyMock)
def test_private_attribute_value_change_decorator_success(self, private_mock):
obj = Private()
private_mock.return_value = 'string'
self.assertEqual('string', obj._Private__attr)
def test_private_attribute_value_change_context_manager_success(self):
with patch.object(Private, '_Private__attr', new_callable=PropertyMock) as o_mock:
obj = Private()
o_mock.return_value = 'mocked value'
self.assertEqual('mocked value', obj._Private__attr)
if __name__ == '__main__':
main()
Modifications to your example:
from unittest import TestCase, mock, main
class A:
__user_id = 3
def __init__(self, user, group):
"""
Your logic is missing - obviously
:param user:
:param group:
"""
def __get_group_members(self):
"""
Your logic is missing - obviously
:return:
"""
return ['user_1', 'user_2']
def is_member_of(self, group_name):
members = self.__get_group_members(group_name)
# will return if the user is a member of the group
return self.__user_id in members
class GroupTest(TestCase):
group_data = [1, 2]
user_id = 'test_user_id'
#mock.patch.object(A, '_A__get_group_members')
#mock.patch.object(A, '_A__user_id', new_callable=mock.PropertyMock)
def test_this_is_my_first_success(self, user_id_mock: mock.PropertyMock, get_group_members_mock: mock.MagicMock):
get_group_members_mock.return_value = self.group_data
user_id_mock.return_value = 3
x = A('user_3', 'this_group')
self.assertEqual(False, x.is_member_of('test_group'))
#mock.patch.object(A, '_A__get_group_members')
#mock.patch.object(A, '_A__user_id', new_callable=mock.PropertyMock)
def test_this_is_my_first_failure(self, user_id_mock: mock.PropertyMock, get_group_members_mock: mock.MagicMock):
get_group_members_mock.return_value = self.group_data
user_id_mock.return_value = 1
x = A('user_1', 'this_group')
self.assertEqual(True, x.is_member_of('test_group'))
if __name__ == '__main__':
main()
If you know you'll mock these two attributes in all test cases you can add the decorators on the class level and expect the arguments like-wise.
In the case where the attribute is set through the __init__ or any other method, you could simply alter it as shown below.
from unittest import TestCase, mock, main
class A:
def __init__(self, user, group):
"""
Your logic is missing - obviously
:param user:
:param group:
"""
def __get_group_members(self):
"""
Your logic is missing - obviously
:return:
"""
return ['user_1', 'user_2']
def is_member_of(self, group_name):
members = self.__get_group_members(group_name)
# will return if the user is a member of the group
return self.__user_id in members
class GroupTest(TestCase):
group_data = [1, 2]
user_id = 'test_user_id'
#mock.patch.object(A, '_A__get_group_members')
def test_this_is_my_first_success(self, get_group_members_mock: mock.MagicMock):
x = A('user_3', 'this_group')
x._A__user_id = 5
get_group_members_mock.return_value = self.group_data
self.assertEqual(False, x.is_member_of('test_group'))
#mock.patch.object(A, '_A__get_group_members')
def test_this_is_my_first_failure(self, get_group_members_mock: mock.MagicMock):
get_group_members_mock.return_value = self.group_data
x = A('user_1', 'this_group')
x._A__user_id = 1
self.assertEqual(True, x.is_member_of('test_group'))
if __name__ == '__main__':
main()

Python parent class modifiable by child class or dynamic attribute?

So I am not even sure if what I want to do is possible but I thought I would ask and find out.
I want to build a chef "databag" via python. This is pretty much just a python dictionary. There are other things that need to happen with this databag that are encapsulated in the Databag class.
Now for the meat of the question...
I want to add key/values to this dictionary but need to build it in a way that is easily extensible. NOTE: the autodict is a class that makes it so you can build a dictionary using dot notation.
Here is what I am trying to do:
databag = Databag(
LogGroup=Sub("xva-${environment}-${uniqueid}-mygroup"),
RunList=[
"mysetup::default",
"consul::client"
]
)
databag.Consul() <-- Trying to add consul key/values to databag
print(databag.to_dict())
print(databag.to_string_list())
So you can see how I add the "consul" key values to the already existing databag object.
Here are the class definitions. I know this is wrong which is why I am here to see if this is even possible.
Databag Class
class Databag(object):
def __init__(self,uniqueid=Ref("uniqueid"),environment=Ref("environment"),LogGroup=None,RunList=[]):
self.databag = autodict()
self.databag.uniqueid = uniqueid
self.databag.environment = environment
self.databag.log.group = LogGroup
self.runlist=RunList
def to_string_list(self):
return self.convert_databag_to_string(self.databag)
def to_dict(self):
return self.databag
def get_runlist(self):
return self.convert_to_runlist_string(self.runlist)
Consul Class
class Consul(Databag):
def __init__(self, LogGroup=None):
if LogGroup == None:
Databag.consul.log.group = Databag.log.group
else:
Databag.consul.log.group = LogGroup
As you can see the Consul class is supposed to access the databag dictionary of the Databag class and add the "consul" variables, almost like an attribute. However, I don't want to add a new function to the databag class every time otherwise that class will end up being very, very large.
I was able to get something like this to work with the following method. Although I am up for an suggestions to get this to work. I just read the help posted on this link:
http://www.qtrac.eu/pyclassmulti.html
EDIT: This method is a lot easier:
Note: This uses the exact same implementation of the old method.
consul.py
from classes.databag.utils import *
class Consul:
def Consul(self, LogGroup=None):
if LogGroup == None:
self.databag.consul.log.group = self.databag.log.group
else:
self.databag.consul.log.group = LogGroup
databag.py
from classes.databag.utils import autodict
from classes.databag import consul
class Databag(consul.Consul):
def __init__(self,uniqueid=Ref("uniqueid"),environment=Ref("environment"),LogGroup=None,RunList=[]):
self.databag = autodict()
self.databag.uniqueid = uniqueid
...
...
Folder Structure
/classes/
databag/
utils.py
databag.py
consul.py
testing.py
---- OLD METHOD -----
How I implemented it
from classes.databag.databag import *
databag = Databag(
LogGroup=Sub("xva-${environment}-${uniqueid}-traefik"),
RunList=[
"mysetup::default",
"consul::client"
]
)
databag.Consul()
print(databag.to_dict())
print(databag.to_string_list())
lib.py
def add_methods_from(*modules):
def decorator(Class):
for module in modules:
for method in getattr(module, "__methods__"):
setattr(Class, method.__name__, method)
return Class
return decorator
def register_method(methods):
def register_method(method):
methods.append(method)
return method
return register_method
databay.py
from classes.databag import lib, consul
#lib.add_methods_from(consul)
class Databag(object):
def __init__(self,uniqueid=Ref("uniqueid"),environment=Ref("environment"),LogGroup=None,RunList=[]):
self.databag = autodict()
self.databag.uniqueid = uniqueid
....
....
consul.py
from classes.databag import lib
__methods__ = []
register_method = lib.register_method(__methods__)
#register_method
def Consul(self, LogGroup=None):
if LogGroup == None:
self.databag.consul.log.group = self.databag.log.group
else:
self.databag.consul.log.group = LogGroup
Folder Structure
/classes/
/databag
lib.py
databag.py
consul.py
utils.py
/testing.py

Dynamic update of class attributes between different classes

I'm trying to manage a directory tree which is created through a hierarchy of Python objects. I want to serialize the top-level object in JSON so I can share 2 things with users: the JSON file along with the directory. I'd like other users to be able to point to that directory, so the problem here is setting that root directory which might change on a different computer.
Here's an example of what I have right now:
import os.path as op
class Top():
def __init__(self, root_dir):
self._root_dir = root_dir
intop = InTop(self.base_dir)
self.intop = intop
#property
def root_dir(self):
return self._root_dir
#root_dir.setter
def root_dir(self, path):
self._root_dir = path
#property
def base_dir(self):
return op.join(self.root_dir, 'Top')
class InTop():
def __init__(self, root_dir):
self._intop_dir = op.join(root_dir, 'InTop')
#property
def intop_dir(self):
return self._intop_dir
#intop_dir.setter
def intop_dir(self, path):
self._intop_dir = path
I'm happy with how this works right now for updating the path in a Top object:
t = Top('~/projects/')
print(t.root_dir) # ~/projects/
print(t.base_dir) # ~/projects/Top
t.root_dir = '~/Downloads/'
print(t.root_dir) # ~/Downloads/
print(t.base_dir) # ~/Downloads/Top
But is there any way for that change to propagate to the InTop object?
t = Top('~/projects/')
print(t.root_dir) # ~/projects/
print(t.base_dir) # ~/projects/Top
print(t.intop.intop_dir) # ~/projects/Top/InTop
t.root_dir = '~/Downloads/'
print(t.root_dir) # ~/Downloads/
print(t.base_dir) # ~/Downloads/Top
print(t.intop.intop_dir) # ~/projects/Top/InTop <--- How to update this?
How do I get that last line to print "~/Downloads/Top/InTop" instead?
Perhaps there is a better way to manage relative file paths like this - if so please let me know.
Thanks in advance!
Figured it out..just needed to set it within the Top setter (also corrected my setters)
import os.path as op
class Top(object):
def __init__(self, root_dir):
self._root_dir = root_dir
intop_obj = InTop(self.top_dir)
self.intop = intop_obj
#property
def root_dir(self):
return self._root_dir
#root_dir.setter
def root_dir(self, path):
self._root_dir = path
self.intop.top_dir = self.top_dir
#property
def top_dir(self):
return op.join(self.root_dir, 'Top')
class InTop(object):
def __init__(self, top_dir):
self._top_dir = top_dir
#property
def top_dir(self):
return self._top_dir
#top_dir.setter
def top_dir(self, top_dir):
self._top_dir = top_dir
#property
def intop_dir(self):
return op.join(self.top_dir, 'InTop')
Gets me:
t = Top('~/projects/')
print(t.root_dir) # ~/projects/
print(t.top_dir) # ~/projects/Top
print(t.intop.intop_dir) # ~/projects/Top/InTop
t.root_dir = '~/Downloads/'
print(t.root_dir) # ~/Downloads/
print(t.top_dir) # ~/Downloads/Top
print(t.intop.intop_dir) # ~/Downloads/Top/InTop

How to access a variable from a class of another module

I am a total python beginner and I have a variable created in a class of a file commandline_reader.py that I want to access from another script. I tried to do it by making the variable global, which doesn't work.
myscript.py:
from commandline_reader import Commandline_Reader
reader = Commandline_Reader('--get_serial_number')
reader.run()
print output
commandline_reader.py:
class Commandline_Reader:
def __init__(self,argString=''):
global output
output = []
def run(self):
# do stuff
a = 'somevariable'
output.append(a)
When I run myscript.py I always get a NameError: name 'output' is not defined. I've read that this is because global variables are only defined within a module. How do I correctly access the output variable in my script?
ouch. The whole reason object oriented programming takes place is to avoid the use of global variables. Make them instance variables to access them anywhere in the class.
class Commandline_Reader:
def __init__(self,argString=''):
self.output = []
def run(self):
# do stuff
a = 'somevariable'
self.output.append(a) #output is now part of the instance Commandline reader and can be accessed anywhere inside the class.
clr = Commandline_Reader(argstring='--get_serial_number')
clr.run()
print clr.output
>>>['somevariable']
Make output an instance attribute:
class Commandline_Reader:
def __init__(self,argString=''):
self.output = [] # note use of self here
def run(self):
# do stuff
a = 'somevariable'
self.output.append(a) # and here
The access it via the instance:
print reader.output
Maybe class attribute is more appropriate for you?
class Commandline_Reader:
output = []
def run(self):
# do stuff
a = 'somevariable'
self.output.append(a)
Just return the Value from the run() Method
myscript.py:
from commandline_reader import Commandline_Reader
reader = Commandline_Reader('--get_serial_number')
output = reader.run()
print output
commandline_reader.py:
class Commandline_Reader:
def __init__(self,argString=''):
self.output = []
def run(self):
# do stuff
a = 'somevariable'
self.output.append(a)
return self.output

Categories

Resources