Skip to content

Conversation

@ericpruitt
Copy link

This allows the user to provide an IP address in the form of an ipaddress.IPv4Address or ipaddress.IPv6Address as an argument for the "get" method.

@oschwald
Copy link
Member

For this to be merged, the C extension would have to be updated to support it and there would need to be tests.

@ericpruitt
Copy link
Author

I'm fine with updating the C extension and tests if the feature is welcomed. I'll follow-up once I've done that.

@oschwald
Copy link
Member

As long as the feature works on all supported Python versions with both the C extension and pure Python implementation and doesn't affect performance, I'd be happy to include it.

The "test_too_many_get_args" methods was not actually testing what the
author intended because unittest.TestCase.assertRaises method accepts
multiple function arguments as positional parameters, not tuples as used
in the code, so the assertRaises invocation has been updated to correct
this mistake.
@coveralls
Copy link

coveralls commented Jul 30, 2019

Coverage Status

Coverage increased (+0.4%) to 94.921% when pulling 83832e1 on ericpruitt:ipaddress-arg-1 into cf42a02 on maxmind:master.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.4%) to 94.921% when pulling 332cdd0 on ericpruitt:ipaddress-arg-1 into cf42a02 on maxmind:master.

1 similar comment
@coveralls
Copy link

Coverage Status

Coverage increased (+0.4%) to 94.921% when pulling 332cdd0 on ericpruitt:ipaddress-arg-1 into cf42a02 on maxmind:master.

@ericpruitt
Copy link
Author

The code is ready to be reviewed. This is my first time working on Python C extension code, and I may have not handled the reference counting correctly. I originally had more Py_DECREF calls than I currently do. I initially thought they were correct, but removing them resolved some segfaults I observed running the test suite.

This reverts commit b190d97 because it
breaks continuous integration.
The changes the PY_MAJOR_VERSION checks to match the expression used by
the maintainers.
@ericpruitt
Copy link
Author

@oschwald Any idea when someone will be able to review this PR?

@ericpruitt
Copy link
Author

@rafl or @andyjack maybe?

@oschwald
Copy link
Member

oschwald commented Aug 8, 2019

@ericpruitt, sorry for the delayed response. I am hoping to get to this soon. The C code will need some closer review. I'll add a couple of comments now however based on a cursory review.

Copy link
Member

@oschwald oschwald left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some initial comments.

return NULL;
}

PyObject* ipaddress_IPv4Address = PyDict_GetItemString(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we be using PyObject_IsInstance or something here?

Copy link
Author

@ericpruitt ericpruitt Aug 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm calling PyObject_IsInstance below. PyObject_IsInstance requires a PyObject as its second argument, so I don't understand how I'm supposed to use PyObject_IsInstance without getting the IPv4Address class first.

if (!is_ipaddress_object) {
PyErr_SetString(PyExc_TypeError, "IP address must be a string,"
" ipaddress.IPv4Address or ipaddress.IPv6Address object");
} else if ((str = PyObject_Str(object)) != NULL) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a little bit weird that we are doing all of these gymnastics and creating these heavy Python string and byte objects. I wonder if we could just get the packed address. Ideally, we wouldn't even bother converting that back to a *char, but it probably doesn't hurt too much to do that conversion.

Copy link
Author

@ericpruitt ericpruitt Aug 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only simpler method I could think of would be casting the argument to a str, but then you lose out on type safety.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could just get the packed address

That is exposed:

~$ python
Python 3.5.3 (default, Sep 27 2018, 17:25:39)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import ipaddress
>>> i = ipaddress.ip_address("127.0.0.1")
>>> i.packed
b'\x7f\x00\x00\x01'

} else if ((str = PyObject_Str(object)) != NULL) {
#if PY_MAJOR_VERSION >= 3
bytes = PyUnicode_AsEncodedString(str, "UTF-8", "strict");
ip_address = PyBytes_AS_STRING(bytes);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this code is kept, I think you can just decrement the reference count on bytes here and get rid of the ones below. There are also leaks from early returns as it is done now.

Copy link
Author

@ericpruitt ericpruitt Aug 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does Python guarantee that garbage collection will not immediately take place when the reference counter drops to 0? The reason I'm i'm decrementing the reference count in more than one place is because the ip_address pointer points into bytes, and if decrementing the reference counter frees that memory, it introduces a use-after-free bug.

char *ip_address = NULL;
PyObject *object = NULL;
#if PY_MAJOR_VERSION >= 3
PyObject *bytes = NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to declare the variables at the top of the function.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do you propose I declare it? I don't think I can declare it inside the "if" condition since that will cause "bytes" to be scoped to that block which will mean it can't be used in the code further down.

Copy link
Author

@ericpruitt ericpruitt Aug 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also can't declare object inside the "if" block because I need to use object in the condition expression.

@oschwald
Copy link
Member

I had some more time to think about this, and I think it would be cleaner using O& with PyArg_ParseTuple and making a converter function. I made a quick sketch of what this might look like. This is only a sketch and it would need to be updated for older Python versions and cleaned up generally.

Based on my testing, this is also much faster, 30% or so. I assume a lot of that is due to just allowing any object with a packed attribute, although there is less Python object creation generally.

@ericpruitt
Copy link
Author

ericpruitt commented Aug 11, 2019

Alright, I'll close out this pull request. You probably want to at least pull in the unit test change I made to fix the arguments for assertRaises.

EDIT: I overlooked that this was branched from my pull request, so that change is already included.

@oschwald
Copy link
Member

#50 adds support for ipaddress objects and will be in the next release.

@ericpruitt
Copy link
Author

#50 adds support for ipaddress objects and will be in the next release.

Thanks!

@oschwald oschwald mentioned this pull request Oct 3, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants