Пример #1
0
def test_suffix_normalize():
    for suffix, expected in REL_URL_TEST_CASES:
        url = URL(REL_URL_BASE)
        url.normalize(suffix)
        assert url.path == URL(expected).path

    return
Пример #2
0
def test_parse_equals_in_qp_value():
    u = URL('http://localhost/?=x=x=x')
    assert u.q[''] == 'x=x=x'
    assert u.to_text() == 'http://localhost/?=x%3Dx%3Dx'

    u = URL('http://localhost/?foo=x=x=x&bar=y')
    assert u.q['foo'] == 'x=x=x'
    assert u.q['bar'] == 'y'
Пример #3
0
def compose_url(base_url, url):
    base_url = URL(base_url)
    url = URL(url)
    if not url.scheme:
        absolute_url = base_url.navigate(url.to_text())
    else:
        absolute_url = url
    return absolute_url.to_text()
Пример #4
0
def test_chained_navigate(expected, base, paths):
    """Chained :meth:`navigate` calls produces correct results."""
    url = URL(base)

    for path in paths:
        url = url.navigate(path)

    assert expected == url.to_text()
Пример #5
0
def test_rel_navigate():
    for suffix, expected in REL_URL_TEST_CASES:
        url = URL(REL_URL_BASE)
        new_url = url.navigate(suffix)
        assert new_url.to_text() == expected

        new_url = url.navigate(URL(suffix))
        assert new_url.to_text() == expected

    return
Пример #6
0
def test_create_tag(user: UserClient):
    """Creates a tag specifying a custom organization."""
    org = Organization(name='bar', tax_id='bartax')
    tag = Tag(id='bar-1', org=org, provider=URL('http://foo.bar'), owner_id=user.user['id'])
    db.session.add(tag)
    db.session.commit()
    tag = Tag.query.one()
    assert tag.id == 'bar-1'
    assert tag.provider == URL('http://foo.bar')
    res, _ = user.get(res=Tag, item=tag.code, status=422)
    assert res['type'] == 'TagNotLinked'
Пример #7
0
def _test_bad_utf8():  # not part of the API
    bad_bin_url = 'http://xn--9ca.com/%00%FF/%C3%A9'
    url = URL(bad_bin_url)

    expected = ('http://\N{LATIN SMALL LETTER E WITH ACUTE}.com/'
                '%00%FF/'
                '\N{LATIN SMALL LETTER E WITH ACUTE}')

    actual = url.to_text()

    assert expected == actual
Пример #8
0
def test_unicodey():
    unicodey = (u'http://\N{LATIN SMALL LETTER E WITH ACUTE}.com/'
                u'\N{LATIN SMALL LETTER E}\N{COMBINING ACUTE ACCENT}'
                u'?\N{LATIN SMALL LETTER A}\N{COMBINING ACUTE ACCENT}='
                u'\N{LATIN SMALL LETTER I}\N{COMBINING ACUTE ACCENT}'
                u'#\N{LATIN SMALL LETTER U}\N{COMBINING ACUTE ACCENT}')
    url = URL(unicodey)
    assert url.host == u'é.com'
    assert url.path_parts[1] == u'\N{LATIN SMALL LETTER E}\N{COMBINING ACUTE ACCENT}'
    assert url.to_text(full_quote=False) == unicodey
    fully_quoted = 'http://xn--9ca.com/%C3%A9?%C3%A1=%C3%AD#%C3%BA'
    assert url.to_text(full_quote=True) == fully_quoted
Пример #9
0
 def prepare_urls(self, msg_or_text, direct_urls=False):
     if isinstance(msg_or_text, Message):
         urls = []
         url_entities = msg_or_text.parse_entities(
             types=[MessageEntity.URL])
         for entity in url_entities:
             url_str = url_entities[entity]
             logger.debug("Entity URL Parsed: %s", url_str)
             if "://" not in url_str:
                 url_str = "http://{}".format(url_str)
             urls.append(URL(url_str))
         text_link_entities = msg_or_text.parse_entities(
             types=[MessageEntity.TEXT_LINK])
         for entity in text_link_entities:
             url_str = entity.url
             logger.debug("Entity Text Link Parsed: %s", url_str)
             urls.append(URL(url_str))
     else:
         urls = find_all_links(msg_or_text, default_scheme="http")
     urls_dict = {}
     for url in urls:
         url_text = url.to_text(True)
         url_parts_num = len([part for part in url.path_parts if part])
         try:
             if (
                     # SoundCloud: tracks, sets and widget pages, no /you/ pages
                 (self.SITES["sc"] in url.host and
                  (2 <= url_parts_num <= 3
                   or self.SITES["scapi"] in url_text) and
                  (not "you" in url.path_parts)) or
                     # Bandcamp: tracks and albums
                 (self.SITES["bc"] in url.host and
                  (2 <= url_parts_num <= 2)) or
                     # YouTube: videos and playlists
                 (self.SITES["yt"] in url.host and
                  ("youtu.be" in url.host or "watch" in url.path
                   or "playlist" in url.path))):
                 if direct_urls or self.SITES["yt"] in url.host:
                     urls_dict[url_text] = get_direct_urls(url_text)
                 else:
                     urls_dict[url_text] = "http"
             elif not any(
                 (site in url.host for site in self.SITES.values())):
                 urls_dict[url_text] = get_direct_urls(url_text)
         except ProcessExecutionError:
             logger.debug("youtube-dl get url failed: %s", url_text)
         except URLError as exc:
             urls_dict[url_text] = exc.status
     return urls_dict
Пример #10
0
def get_gh_project_info(url):
    ret = {}

    org, repo = URL(url).path_parts[1:]
    gh_url = URL('https://api.github.com/repos')
    gh_url.path_parts += (org, repo)

    project_url = gh_url.to_text()
    project_data = _get_gh_json(project_url)
    ret['star_count'] = project_data['stargazers_count']

    gh_url.path_parts += ('tags', )
    tags_url = gh_url.to_text()
    tags_data = _get_gh_json(tags_url)
    vtags_data = [td for td in tags_data if match_vtag(td['name'], PREFIXES)]
    ret['release_count'] = len(vtags_data)

    first_release = vtags_data[-1]
    first_release_data = _get_gh_rel_data(first_release, PREFIXES)
    for k, v in first_release_data.items():
        ret['first_release_%s' % k] = v

    latest_release = vtags_data[0]
    latest_release_data = _get_gh_rel_data(latest_release, PREFIXES)
    for k, v in latest_release_data.items():
        ret['latest_release_%s' % k] = v

    zv_releases = [
        rel for rel in vtags_data
        if match_vtag(rel['name'], PREFIXES).group('major') == '0'
    ]
    ret['release_count_zv'] = len(zv_releases)
    print ' .. %s releases, %s 0ver' % (ret['release_count'],
                                        ret['release_count_zv'])

    is_zerover = zv_releases[0] == latest_release

    ret['is_zerover'] = is_zerover

    if is_zerover:
        return ret

    last_zv_release = zv_releases[0]
    last_zv_release_data = _get_gh_rel_data(last_zv_release, PREFIXES)

    for k, v in last_zv_release_data.items():
        ret['last_zv_release_%s' % k] = v

    return ret
Пример #11
0
 def parse_uri(cls, uri):
     """Parses a BIP21 Payment URI into this class"""
     parsed = URL(uri)
     currency = parsed.scheme
     address = parsed.path
     amount = parsed.qp['amount']
     return cls(currency, address, amount)
Пример #12
0
 def open(self,
          uri: str,
          res: str = None,
          status: Status = 200,
          query: Query = tuple(),
          accept=JSON,
          content_type=JSON,
          item=None,
          headers: dict = None,
          token: str = None,
          **kw) -> Res:
     headers = headers or {}
     if res:
         resource_url = self.application.resources[res].url_prefix + '/'
         uri = URL(uri).navigate(resource_url).to_text()
     if token:
         headers['Authorization'] = 'Basic {}'.format(token)
     res = super().open(uri, status, query, accept, content_type, item,
                        headers, **kw)
     # ereuse-utils checks for status code
     # here we check for specific type
     # (when response: {'type': 'foobar', 'code': 422})
     _status = getattr(status, 'code', status)
     if not isinstance(status, int) and res[1].status_code == _status:
         assert status.__name__ == res[0]['type'], \
             'Expected exception {0} but it was {1}'.format(status.__name__, res[0]['type'])
     return res
Пример #13
0
 def parse_uri(cls, uri):
     """Parses a EIP681 Payment URI into this class"""
     parsed = URL(uri)
     currency = parsed.scheme
     address = parsed.path
     value = parsed.qp['value']
     return cls(currency, address, value)
Пример #14
0
def test_get_tags_endpoint(user: UserClient, app: Devicehub,
                           requests_mock: requests_mock.mocker.Mocker):
    """Performs GET /tags after creating 3 tags, 2 printable and one
    not. Only the printable ones are returned.
    """
    # Prepare test
    with app.app_context():
        org = Organization(name='bar', tax_id='bartax')
        tag = Tag(id='bar-1', org=org, provider=URL('http://foo.bar'), owner_id=user.user['id'])
        db.session.add(tag)
        db.session.commit()
        assert not tag.printable

    requests_mock.post('https://example.com/',
                       # request
                       request_headers={
                           'Authorization': 'Basic {}'.format(DevicehubClient.encode_token(
                               '52dacef0-6bcb-4919-bfed-f10d2c96ecee'))
                       },
                       # response
                       json=['tag1id', 'tag2id'],
                       status_code=201)
    user.post({}, res=Tag, query=[('num', 2)])

    # Test itself
    data, _ = user.get(res=Tag)
    assert len(data['items']) == 2, 'Only 2 tags are printable, thus retreived'
    # Order is created descending
    assert data['items'][0]['id'] == 'tag2id'
    assert data['items'][0]['printable']
    assert data['items'][1]['id'] == 'tag1id'
    assert data['items'][1]['printable'], 'Tags made this way are printable'
Пример #15
0
def test_utf8_url():
    url_bytes = (b'http://\xd9\x85\xd8\xab\xd8\xa7\xd9\x84'
                 b'.\xd8\xa2\xd8\xb2\xd9\x85\xd8\xa7'
                 b'\xdb\x8c\xd8\xb4\xdb\x8c')
    url = URL(url_bytes)
    assert url.scheme == 'http'
    assert url.host == u'مثال.آزمایشی'
Пример #16
0
def test_quoted_userinfo():
    url = URL('http://wikipedia.org')
    url.username = u'user'
    url.password = u'p@ss'
    assert url.to_text(full_quote=True) == 'http://*****:*****@wikipedia.org'

    url = URL(u'http://beyonc\xe9:b\[email protected]')
    # assert url.to_text(full_quote=False) == u'http://beyoncé:b%C3%[email protected]'
    assert url.to_text(full_quote=True) == u'http://beyonc%C3%A9:b%C3%[email protected]'
Пример #17
0
 def _get_links(self, group, name):
     link_list = list(self.get_config(group, name, []))
     for link in link_list:
         if link['href'] and URL(link['href']).host:
             link['is_external'] = True
         else:
             link['is_external'] = False
     return link_list
Пример #18
0
def test_idna():
    u1 = URL(u'http://bücher.ch')
    assert u1.host == u'bücher.ch'
    assert u1.to_text(full_quote=True) == 'http://xn--bcher-kva.ch'
    assert u1.to_text(full_quote=False) == u'http://bücher.ch'

    u2 = URL('https://xn--bcher-kva.ch')
    assert u2.host == u'bücher.ch'
    assert u2.to_text(full_quote=True) == 'https://xn--bcher-kva.ch'
    assert u2.to_text(full_quote=False) == u'https://bücher.ch'
Пример #19
0
def test_normalize_with_case():
    # from RFC 3986 Section 6.2.2
    url1 = URL('example://a/b/c/%7Bfoo%7D')
    url2 = URL('eXAMPLE://a/./b/../b/%63/%7bfoo%7d')

    assert url1 != url2

    url1.normalize()
    url2.normalize()

    assert url1 == url2
Пример #20
0
def test_tag_create_etags_cli(app: Devicehub, user: UserClient):
    """Creates an eTag through the CLI."""
    # todo what happens to organization?
    owner_id = user.user['id']
    runner = app.test_cli_runner()
    args = ('tag', 'add', '-p', 'https://t.ereuse.org', '-s', 'foo', 'DT-BARBAR', '-u', owner_id)
    runner.invoke(*args)
    with app.app_context():
        tag = Tag.query.one()  # type: Tag
        assert tag.id == 'dt-barbar'
        assert tag.secondary == 'foo'
        assert tag.provider == URL('https://t.ereuse.org')
Пример #21
0
def test_etag_secondary(client: Client, app: Teal):
    """Tests creating, linking and accessing an ETag through
    its secondary (NFC) id."""
    with app.app_context():
        et = ETag(secondary='NFCID')
        db.session.add(et)
        db.session.commit()
    client.get('/', item='NFCID', accept=ANY, status=400)
    with app.app_context():
        tag = ETag.query.filter_by(secondary='NFCID').one()
        tag.devicehub = URL('https://dh.com')
        db.session.commit()
    _, r = client.get('/', item='NFCID', accept=ANY, status=302)
    assert r.location == 'https://dh.com/tags/FO-3MP5M/device'
Пример #22
0
def get_link_text(urls):
    link_text = ""
    for i, url in enumerate(urls):
        link_text += "[Source Link #{}]({}) | `{}`\n".format(str(i + 1), url, URL(url).host)
        direct_urls = urls[url].splitlines()
        for direct_url in direct_urls:
            if "http" in direct_url:
                content_type = ""
                if "googlevideo" in direct_url:
                    if "audio" in direct_url:
                        content_type = "Audio"
                    else:
                        content_type = "Video"
                # direct_url = shorten_url(direct_url)
                link_text += "• {} [Direct Link]({})\n".format(content_type, direct_url)
    return link_text
Пример #23
0
def test_create_tags_cli(runner: FlaskCliRunner, client: Client):
    """Tests creating a tag."""
    with NamedTemporaryFile('r+') as f:
        result = runner.invoke(args=('create-tags', '100', '--csv', f.name,
                                     '--etag'),
                               catch_exceptions=False)
        assert result.exit_code == 0
        urls = tuple(csv.reader(f))
        assert len(urls) == 100
        for url, *_ in urls:
            assert 'http://foo.bar/' in url
            assert len(url) == 23
            # Tag exists but it has not been linked
            client.get(res=Tag.t,
                       item=URL(url).path_parts[1],
                       status=NoRemoteTag)
Пример #24
0
def get_link_text(urls):
    link_text = ""
    for i, url in enumerate(urls):
        link_text += "[Source Link #{}]({}) | `{}`\n".format(str(i + 1), url, URL(url).host)
        direct_urls = urls[url].splitlines()
        for direct_url in direct_urls:
            if "http" in direct_url:
                content_type = ""
                if "googlevideo" in direct_url:
                    if "audio" in direct_url:
                        content_type = "Audio"
                    else:
                        content_type = "Video"
                # direct_url = shorten_url(direct_url)
                link_text += "• {} [Direct Link]({})\n".format(content_type, direct_url)
    link_text += "\n*Note:* Final download URLs are only guaranteed to work on the same machine/IP where extracted"
    return link_text
Пример #25
0
 def import_tags(self, csv: Path):
     """Imports the database from a CSV from ``export``.
     This truncates only the Tag table.
     """
     db.session.execute('TRUNCATE TABLE {} RESTART IDENTITY'.format(
         Tag.__table__.name))
     max_id = 0
     """The maximum ID of the tags. Sequence is reset to this + 1."""
     with csv.open() as f:
         for row in csvm.reader(f):
             id, secondary, dh, type, updated, created = row
             cls = Tag if type == 'Tag' else ETag
             dh = URL(dh) if dh else None
             t = cls(id=id, secondary=secondary)
             db.session.add(t)
             max_id = max(max_id, cls.decode(id))
     db.session.execute(
         'ALTER SEQUENCE tag_id RESTART WITH {}'.format(max_id + 1))
     db.session.commit()
Пример #26
0
 def get_link_text(self, urls):
     link_text = ""
     for i, url in enumerate(urls):
         link_text += "[Source Link #{}]({}) | `{}`\n".format(str(i + 1), url, URL(url).host)
         direct_urls = urls[url].splitlines()
         for j, direct_url in enumerate(direct_urls):
             if "http" in direct_url:
                 content_type = ""
                 if "googlevideo" in direct_url:
                     if "audio" in direct_url:
                         content_type = "Audio"
                     else:
                         content_type = "Video"
                 if self.shortener:
                     try:
                         direct_url = self.shortener.short(direct_url)
                         # botan.shorten_url(original_url, botan_token, uid)
                     except:
                         pass
                 link_text += "• {} [Direct Link]({})\n".format(content_type, direct_url)
     return link_text
Пример #27
0
 def prepare_urls(self, msg=None, text=None, get_direct_urls=False):
     if text:
         urls = find_all_links(text, default_scheme="http")
     elif msg:
         urls = []
         for url_str in msg.parse_entities(types=["url"]).values():
             if "://" not in url_str:
                 url_str = "http://" + url_str
             urls.append(URL(url_str))
     else:
         logger.debug("Text or msg is required")
         return
     urls_dict = {}
     for url in urls:
         url_text = url.to_text(True)
         url_parts_num = len([part for part in url.path_parts if part])
         if (
                 # SoundCloud: tracks, sets and widget pages
             (self.SITES["sc"] in url.host and
              (2 <= url_parts_num <= 3 or self.SITES["scapi"] in url_text))
                 or
                 # Bandcamp: tracks and albums
             (self.SITES["bc"] in url.host and (2 <= url_parts_num <= 2)) or
                 # YouTube: videos and playlists
             (self.SITES["yt"] in url.host and
              ("youtu.be" in url.host or "watch" in url.path
               or "playlist" in url.path))):
             if get_direct_urls or self.SITES["yt"] in url.host:
                 direct_urls = self.youtube_dl_get_direct_urls(url_text)
                 if direct_urls:
                     urls_dict[url_text] = direct_urls
             else:
                 urls_dict[url_text] = "http"
         elif not any((site in url.host for site in self.SITES.values())):
             direct_urls = self.youtube_dl_get_direct_urls(url_text)
             if direct_urls:
                 urls_dict[url_text] = direct_urls
     if not urls_dict:
         logger.info("No supported URLs found")
     return urls_dict
Пример #28
0
def test_delete_tags(user: UserClient, client: Client):
    """Delete a named tag."""
    # Delete Tag Named
    g.user = User.query.one()
    pc = Desktop(serial_number='sn1', chassis=ComputerChassis.Tower, owner_id=user.user['id'])
    db.session.add(pc)
    db.session.commit()
    tag = Tag(id='bar', owner_id=user.user['id'], device_id=pc.id)
    db.session.add(tag)
    db.session.commit()
    tag = Tag.query.all()[-1]
    assert tag.id == 'bar'
    # Is not possible delete one tag linked to one device
    res, _ = user.delete(res=Tag, item=tag.id, status=422)
    msg = 'The tag bar is linked to device'
    assert msg in res['message'][0]

    tag.device_id = None
    db.session.add(tag)
    db.session.commit()
    # Is not possible delete one tag from an anonymous user
    client.delete(res=Tag, item=tag.id, status=401)

    # Is possible delete one normal tag
    user.delete(res=Tag, item=tag.id)
    user.get(res=Tag, item=tag.id, status=404)

    # Delete Tag UnNamed
    org = Organization(name='bar', tax_id='bartax')
    tag = Tag(id='bar-1', org=org, provider=URL('http://foo.bar'), owner_id=user.user['id'])
    db.session.add(tag)
    db.session.commit()
    tag = Tag.query.all()[-1]
    assert tag.id == 'bar-1'
    res, _ = user.delete(res=Tag, item=tag.id, status=422)
    msg = 'This tag {} is unnamed tag. It is imposible delete.'.format(tag.id)
    assert msg in res['message']
    tag = Tag.query.all()[-1]
    assert tag.id == 'bar-1'
Пример #29
0
    def set_tags(self, devicehub: str, starting_tag: int, ending_tag: int,
                 csv: Path):
        """
        "Sends" the tags to the specific devicehub,
        so they can only be linked in that devicehub.

        Actual implementation does not send them but rather update the
        internal database as it did send them. You will need to create
        them manually in the destination devicehub.

        This method in the future will probably actually (virtually)
        send them.
        """
        assert starting_tag < ending_tag
        assert URL(devicehub) and devicehub[
            -1] != '/', 'Provide a valid URL without leading slash'
        tags = Tag.query.filter(between(Tag.id, starting_tag,
                                        ending_tag)).all()
        for tag in tags:
            tag.devicehub = devicehub
        db.session.commit()
        self.tags_to_csv(csv, tags)
        print('All tags set to {}'.format(devicehub))
Пример #30
0
def test_inventory_create_delete_user(cli, tdb1, tdb2):
    """Tests creating two inventories with users, one user has
    access to the first inventory and the other to both. Finally, deletes
    the first inventory, deleting only the first user too.
    """
    # Create first DB
    cli.inv('tdb1')
    cli.invoke('inv', 'add', '-n', 'Test DB1', '-on', 'ACME DB1', '-oi',
               'acme-id', '-tu', 'https://example.com', '-tt',
               '3c66a6ad-22de-4db6-ac46-d8982522ec40', '--common')

    # Create an user for first DB
    cli.invoke('user', 'add', '*****@*****.**', '-a', 'Foo', '-c', 'ES', '-p',
               'Such password')

    with tdb1.app_context():
        # There is a row for the inventory
        inv = Inventory.query.one()  # type: Inventory
        assert inv.id == 'tdb1'
        assert inv.name == 'Test DB1'
        assert inv.tag_provider == URL('https://example.com')
        assert inv.tag_token == UUID('3c66a6ad-22de-4db6-ac46-d8982522ec40')
        assert db.has_schema('tdb1')
        org = Organization.query.one()  # type: Organization
        # assert inv.org_id == org.id
        assert org.name == 'ACME DB1'
        assert org.tax_id == 'acme-id'
        user = User.query.one()  # type: User
        assert user.email == '*****@*****.**'

    cli.inv('tdb2')
    # Create a second DB
    # Note how we don't create common anymore
    cli.invoke('inv', 'add', '-n', 'Test DB2', '-on', 'ACME DB2', '-oi',
               'acme-id-2', '-tu', 'https://example.com', '-tt',
               'fbad1c08-ffdc-4a61-be49-464962c186a8')
    # Create an user for with access for both DB
    cli.invoke('user', 'add', '*****@*****.**', '-a', 'Bar', '-p', 'Wow password')

    with tdb2.app_context():
        inventories = Inventory.query.all()  # type: List[Inventory]
        assert len(inventories) == 2
        assert inventories[0].id == 'tdb1'
        assert inventories[1].id == 'tdb2'
        assert db.has_schema('tdb2')
        org_db2 = Organization.query.one()
        assert org_db2 != org
        assert org_db2.name == 'ACME DB2'
        users = User.query.all()  # type: List[User]
        assert users[0].email == '*****@*****.**'
        assert users[1].email == '*****@*****.**'

    # Delete tdb1
    cli.inv('tdb1')
    cli.invoke('inv', 'del', '--yes')

    with tdb2.app_context():
        # There is only tdb2 as inventory
        inv = Inventory.query.one()  # type: Inventory
        assert inv.id == 'tdb2'
        # User [email protected] is deleted because it only
        # existed in tdb1, but not [email protected] which existed
        # in another inventory too (tdb2)
        user = User.query.one()  # type: User
        assert user.email == '*****@*****.**'
        assert not db.has_schema('tdb1')
        assert db.has_schema('tdb2')
Пример #31
0
def get_gh_project_info(info):
    ret = {}
    url = info.get('gh_url')
    if url is None:
        return ret

    org, repo = URL(url.rstrip('/')).path_parts[1:]
    gh_url = URL('https://api.github.com/repos')
    gh_url.path_parts += (org, repo)

    project_url = gh_url.to_text()
    project_data = _get_gh_json(project_url)
    ret['star_count'] = project_data['stargazers_count']

    gh_url.path_parts += ('tags', )
    tags_url = gh_url.to_text()
    tags_data = _get_gh_json(tags_url)
    vtags_data = [td for td in tags_data if match_vtag(td['name'], PREFIXES)]

    ret['release_count'] = len(vtags_data)

    latest_release = vtags_data[0]
    latest_release_data = _get_gh_rel_data(latest_release, PREFIXES)
    for k, v in latest_release_data.items():
        ret['latest_release_%s' % k] = v

    vtags_data.sort(key=lambda x: version_key(x['name'], PREFIXES),
                    reverse=True)

    first_release_version = info.get('first_release_version')
    if first_release_version is None:
        first_release = [
            v for v in vtags_data
            if version_key(v['name']) < version_key(latest_release['name'])
        ][-1]
    else:
        first_release = [
            v for v in vtags_data if v['name'] == first_release_version
        ][0]
    first_release_data = _get_gh_rel_data(first_release, PREFIXES)
    for k, v in first_release_data.items():
        ret['first_release_%s' % k] = v

    zv_releases = [
        rel for rel in vtags_data
        if match_vtag(rel['name'], PREFIXES).group('major') == '0'
    ]
    ret['release_count_zv'] = len(zv_releases)
    print ' .. %s releases, %s 0ver' % (ret['release_count'],
                                        ret['release_count_zv'])

    is_zerover = latest_release in zv_releases

    ret['is_zerover'] = is_zerover

    if is_zerover:
        return ret

    last_zv_release = zv_releases[0]
    first_nonzv_release = vtags_data[vtags_data.index(last_zv_release) - 1]
    first_nonzv_release_data = _get_gh_rel_data(first_nonzv_release, PREFIXES)

    ret['last_zv_release_version'] = last_zv_release['name']
    for k, v in first_nonzv_release_data.items():
        ret['first_nonzv_release_%s' % k] = v

    #import pdb;pdb.set_trace()

    return ret