pax_global_header00006660000000000000000000000064127605222270014517gustar00rootroot0000000000000052 comment=21d4f114a781af6e125d29153e514d47e76dd8ac aiodns-aiodns-1.1.1/000077500000000000000000000000001276052222700142475ustar00rootroot00000000000000aiodns-aiodns-1.1.1/.gitignore000066400000000000000000000002531276052222700162370ustar00rootroot00000000000000# python specific *.pyc *.so *.pyd build/* dist/* MANIFEST __pycache__/ *.egg-info/ # generic files to ignore *~ *.lock *.DS_Store *.swp *.out .tox/ deps/ docs/_build/ aiodns-aiodns-1.1.1/.travis.yml000066400000000000000000000002031276052222700163530ustar00rootroot00000000000000language: python sudo: false python: - "2.7" - "3.2" - "3.3" - "3.4" install: - pip install . script: - ./tests.py aiodns-aiodns-1.1.1/ChangeLog000066400000000000000000000012551276052222700160240ustar00rootroot000000000000001.1.1 ===== - Use per-version requires for wheels 1.1.0 ===== - Add DNSResolver.gethostbyname() - Build universal wheels 1.0.1 ===== - Fix including tests and ChangeLog in source distributions 1.0.0 ===== - Use pycares >= 1.0.0 - Fix tests 0.3.2 ===== - setup: Fix decoding in non-UTF-8 environments 0.3.1 ===== - Adapt to Trollius package rename - Fixed stopping watching file descriptor 0.3.0 ===== - Add DNSResolver.cancel method - Handle case when the Future returned by query() is cancelled 0.2.0 ===== - Add support for Trollius - Don't make query() a coroutine, just return the future - Raise ValueError if specified query type is invalid 0.1.0 ===== - Initial release aiodns-aiodns-1.1.1/LICENSE000066400000000000000000000020561276052222700152570ustar00rootroot00000000000000Copyright (C) 2014 by Saúl Ibarra Corretgé Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. aiodns-aiodns-1.1.1/MANIFEST.in000066400000000000000000000001751276052222700160100ustar00rootroot00000000000000include README.rst LICENSE ChangeLog include setup.py tests.py recursive-exclude * __pycache__ recursive-exclude * *.py[co] aiodns-aiodns-1.1.1/README.rst000066400000000000000000000057021276052222700157420ustar00rootroot00000000000000=============================== Simple DNS resolver for asyncio =============================== .. image:: https://secure.travis-ci.org/saghul/aiodns.png?branch=master :target: http://travis-ci.org/saghul/aiodns aiodns provides a simple way for doing asynchronous DNS resolutions with a synchronous looking interface by using `pycares `_. Example ======= :: import asyncio import aiodns loop = asyncio.get_event_loop() resolver = aiodns.DNSResolver(loop=loop) f = resolver.query('google.com','A') result = loop.run_until_complete(f) print(result) The following query types are supported: A, AAAA, CNAME, MX, NAPTR, NS, PTR, SOA, SRV, TXT. The library supports both *asyncio* and *Trollius*. If you use Python 3 you may use `yield from` statement:: @asyncio.coroutine def func(): result = yield from resolver.query('google.com','A') For Trollius you should use another syntax like:: @trollius.coroutine def func(): result = yield trollius.From(resolver.query('google.com','A')) API === The API is pretty simple, three functions are provided in the ``DNSResolver`` class: * ``query(host, type)``: Do a DNS resolution of the given type for the given hostname. It returns an instance of ``asyncio.Future``. The actual result of the DNS query is taken directly from pycares. As of version 1.0.0 of aiodns (and pycares, for that matter) results are always namedtuple-like objects with different attributes. Please check `the documentation `_ for the result fields. * ``gethostbyname(host, socket_family)``: Do a DNS resolution for the given hostname and the desired type of address family (i.e. ``socket.AF_INET``). While ``query()`` always performs a request to a DNS server, ``gethostbyname()`` first looks into ``/etc/hosts`` and thus can resolve local hostnames (such as ``localhost``). Please check `the documentation `_ for the result fields. The actual result of the call is a ``asyncio.Future``. * ``cancel()``: Cancel all pending DNS queries. All futures will get ``DNSError`` exception set, with ``ARES_ECANCELLED`` errno. Running the test suite ====================== To run the test suite: ``python test_aiodns.py`` Author ====== Saúl Ibarra Corretgé License ======= aiodns uses the MIT license, check LICENSE file. Python versions =============== Python >= 3.4 is natively supported. Python 3.3 supported using the `asyncio package `_. Older Python versions(2.6 - 3.2) are supported using `trollius `_. Contributing ============ If you'd like to contribute, fork the project, make a patch and send a pull request. Have a look at the surrounding code and please, make yours look alike :-) aiodns-aiodns-1.1.1/aiodns/000077500000000000000000000000001276052222700155245ustar00rootroot00000000000000aiodns-aiodns-1.1.1/aiodns/__init__.py000066400000000000000000000073461276052222700176470ustar00rootroot00000000000000import functools try: import asyncio except ImportError: import trollius as asyncio import pycares from . import error __version__ = '1.1.1' __all__ = ('DNSResolver', 'error') READ = 1 WRITE = 2 query_type_map = {'A' : pycares.QUERY_TYPE_A, 'AAAA' : pycares.QUERY_TYPE_AAAA, 'CNAME' : pycares.QUERY_TYPE_CNAME, 'MX' : pycares.QUERY_TYPE_MX, 'NAPTR' : pycares.QUERY_TYPE_NAPTR, 'NS' : pycares.QUERY_TYPE_NS, 'PTR' : pycares.QUERY_TYPE_PTR, 'SOA' : pycares.QUERY_TYPE_SOA, 'SRV' : pycares.QUERY_TYPE_SRV, 'TXT' : pycares.QUERY_TYPE_TXT } class DNSResolver(object): def __init__(self, nameservers=None, loop=None, **kwargs): self.loop = loop or asyncio.get_event_loop() assert self.loop is not None kwargs.pop('sock_state_cb', None) self._channel = pycares.Channel(sock_state_cb=self._sock_state_cb, **kwargs) if nameservers: self.nameservers = nameservers self._read_fds = set() self._write_fds = set() self._timer = None @property def nameservers(self): return self._channel.servers @nameservers.setter def nameservers(self, value): self._channel.servers = value @staticmethod def _callback(fut, result, errorno): if fut.cancelled(): return if errorno is not None: fut.set_exception(error.DNSError(errorno, pycares.errno.strerror(errorno))) else: fut.set_result(result) def query(self, host, qtype): try: qtype = query_type_map[qtype] except KeyError: raise ValueError('invalid query type: {}'.format(qtype)) fut = asyncio.Future(loop=self.loop) cb = functools.partial(self._callback, fut) self._channel.query(host, qtype, cb) return fut def gethostbyname(self, host, family): fut = asyncio.Future(loop=self.loop) cb = functools.partial(self._callback, fut) self._channel.gethostbyname(host, family, cb) return fut def cancel(self): self._channel.cancel() def _sock_state_cb(self, fd, readable, writable): if readable or writable: if readable: self.loop.add_reader(fd, self._handle_event, fd, READ) self._read_fds.add(fd) if writable: self.loop.add_writer(fd, self._handle_event, fd, WRITE) self._write_fds.add(fd) if self._timer is None: self._timer = self.loop.call_later(1.0, self._timer_cb) else: # socket is now closed if fd in self._read_fds: self._read_fds.discard(fd) self.loop.remove_reader(fd) if fd in self._write_fds: self._write_fds.discard(fd) self.loop.remove_writer(fd) if not self._read_fds and not self._write_fds and self._timer is not None: self._timer.cancel() self._timer = None def _handle_event(self, fd, event): read_fd = pycares.ARES_SOCKET_BAD write_fd = pycares.ARES_SOCKET_BAD if event == READ: read_fd = fd elif event == WRITE: write_fd = fd self._channel.process_fd(read_fd, write_fd) def _timer_cb(self): if self._read_fds or self._write_fds: self._channel.process_fd(pycares.ARES_SOCKET_BAD, pycares.ARES_SOCKET_BAD) self._timer = self.loop.call_later(1.0, self._timer_cb) else: self._timer = None def __del__(self): self._channel.destroy() aiodns-aiodns-1.1.1/aiodns/error.py000066400000000000000000000002061276052222700172250ustar00rootroot00000000000000 import pycares for code, name in pycares.errno.errorcode.items(): globals()[name] = code class DNSError(Exception): pass aiodns-aiodns-1.1.1/setup.cfg000066400000000000000000000000341276052222700160650ustar00rootroot00000000000000[bdist_wheel] universal = 1 aiodns-aiodns-1.1.1/setup.py000066400000000000000000000034041276052222700157620ustar00rootroot00000000000000# -*- coding: utf-8 -*- import codecs import re import sys try: from setuptools import setup except ImportError: from distutils.core import setup def get_version(): return re.search(r"""__version__\s+=\s+(?P['"])(?P.+?)(?P=quote)""", open('aiodns/__init__.py').read()).group('version') setup(name = "aiodns", version = get_version(), author = "Saúl Ibarra Corretgé", author_email = "saghul@gmail.com", url = "http://github.com/saghul/aiodns", description = "Simple DNS resolver for asyncio", long_description = codecs.open("README.rst", encoding="utf-8").read(), install_requires = ['pycares>=1.0.0'], extras_require = { ':python_version=="3.3"': ['asyncio'], ':python_version<="3.2"': ['trollius'], }, packages = ['aiodns'], platforms = ["POSIX", "Microsoft Windows"], classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.0", "Programming Language :: Python :: 3.1", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5" ] ) aiodns-aiodns-1.1.1/tasks.py000066400000000000000000000005401276052222700157450ustar00rootroot00000000000000 import invoke # Based on https://github.com/pyca/cryptography/blob/master/tasks.py @invoke.task def release(version): invoke.run("git tag -a aiodns-{0} -m \"aiodns {0} release\"".format(version)) invoke.run("git push --tags") invoke.run("python setup.py sdist") invoke.run("twine upload -r pypi dist/aiodns-{0}*".format(version)) aiodns-aiodns-1.1.1/tests.py000077500000000000000000000115671276052222700160000ustar00rootroot00000000000000#!/usr/bin/env python try: import asyncio except ImportError: import trollius as asyncio import unittest import socket import sys import aiodns import pycares class DNSTest(unittest.TestCase): def setUp(self): self.loop = asyncio.new_event_loop() self.addCleanup(self.loop.close) self.resolver = aiodns.DNSResolver(loop=self.loop) def tearDown(self): self.resolver = None def test_query_a(self): f = self.resolver.query('google.com', 'A') result = self.loop.run_until_complete(f) self.assertTrue(result) def test_query_a_bad(self): f = self.resolver.query('hgf8g2od29hdohid.com', 'A') try: self.loop.run_until_complete(f) except aiodns.error.DNSError as e: self.assertEqual(e.args[0], aiodns.error.ARES_ENOTFOUND) def test_query_aaaa(self): f = self.resolver.query('ipv6.google.com', 'AAAA') result = self.loop.run_until_complete(f) self.assertTrue(result) def test_query_cname(self): f = self.resolver.query('livechat.ripe.net', 'CNAME') result = self.loop.run_until_complete(f) self.assertTrue(result) def test_query_mx(self): f = self.resolver.query('google.com', 'MX') result = self.loop.run_until_complete(f) self.assertTrue(result) def test_query_ns(self): f = self.resolver.query('google.com', 'NS') result = self.loop.run_until_complete(f) self.assertTrue(result) def test_query_txt(self): f = self.resolver.query('google.com', 'TXT') result = self.loop.run_until_complete(f) self.assertTrue(result) def test_query_soa(self): f = self.resolver.query('google.com', 'SOA') result = self.loop.run_until_complete(f) self.assertTrue(result) def test_query_srv(self): f = self.resolver.query('_xmpp-server._tcp.jabber.org', 'SRV') result = self.loop.run_until_complete(f) self.assertTrue(result) def test_query_naptr(self): f = self.resolver.query('sip2sip.info', 'NAPTR') result = self.loop.run_until_complete(f) self.assertTrue(result) def test_query_ptr(self): ip = '8.8.8.8' f = self.resolver.query(pycares.reverse_address(ip), 'PTR') result = self.loop.run_until_complete(f) self.assertTrue(result) def test_query_bad_type(self): self.assertRaises(ValueError, self.resolver.query, 'google.com', 'XXX') def test_query_timeout(self): self.resolver = aiodns.DNSResolver(timeout=0.1, loop=self.loop) self.resolver.nameservers = ['1.2.3.4'] f = self.resolver.query('google.com', 'A') try: self.loop.run_until_complete(f) except aiodns.error.DNSError as e: self.assertEqual(e.args[0], aiodns.error.ARES_ETIMEOUT) def test_query_cancel(self): f = self.resolver.query('google.com', 'A') self.resolver.cancel() try: self.loop.run_until_complete(f) except aiodns.error.DNSError as e: self.assertEqual(e.args[0], aiodns.error.ARES_ECANCELLED) # def test_future_cancel(self): # # TODO: write this in such a way it also works with trollius # f = self.resolver.query('google.com', 'A') # f.cancel() # def coro(): # yield from asyncio.sleep(0.1, loop=self.loop) # yield from f # try: # self.loop.run_until_complete(coro()) # except asyncio.CancelledError as e: # self.assertTrue(e) def test_query_twice(self): if sys.version[:3] >= '3.3': exec('''if 1: @asyncio.coroutine def coro(self, host, qtype, n=2): for i in range(n): result = yield from self.resolver.query(host, qtype) self.assertTrue(result) ''') else: exec('''if 1: @asyncio.coroutine def coro(self, host, qtype, n=2): for i in range(n): result = yield asyncio.From(self.resolver.query(host, qtype)) self.assertTrue(result) ''') self.loop.run_until_complete(locals()['coro'](self, 'gmail.com', 'MX')) def test_gethostbyname(self): f = self.resolver.gethostbyname("google.com", socket.AF_INET) result = self.loop.run_until_complete(f) self.assertTrue(result) def test_gethostbyname_ipv6(self): f = self.resolver.gethostbyname("ipv6.google.com", socket.AF_INET6) result = self.loop.run_until_complete(f) self.assertTrue(result) def test_gethostbyname_bad_family(self): f = self.resolver.gethostbyname("ipv6.google.com", -1) with self.assertRaises(aiodns.error.DNSError): self.loop.run_until_complete(f) if __name__ == '__main__': unittest.main(verbosity=2)