8
\$\begingroup\$

This is a Find unimplemented tasks task from Rosetta Code:

Given the name of a language on Rosetta Code, find all tasks which are not implemented in that language.

There is already a solution that uses urllib, but I decided to add another one using Requests. I've never worked with this library before and would like my code to get reviewed. Any comments are welcome.

"""
Given the name of a language on Rosetta Code,
finds all tasks which are not implemented in that language.
"""
from functools import partial
from operator import itemgetter
from typing import (Dict,
                    Iterator,
                    Union)

import requests

URL = 'http://www.rosettacode.org/mw/api.php'
REQUEST_PARAMETERS = dict(action='query',
                          list='categorymembers',
                          cmlimit=500,
                          rawcontinue=True,
                          format='json')


def unimplemented_tasks(language: str,
                        *,
                        url: str,
                        request_params: Dict[str, Union[str, int, bool]]
                        ) -> Iterator[str]:
    """Yields all unimplemented tasks for a specified language"""
    with requests.Session() as session:
        tasks = partial(tasks_titles,
                        session=session,
                        url=url,
                        **request_params)
        all_tasks = tasks(cmtitle='Category:Programming_Tasks')
        language_tasks = set(tasks(cmtitle=f'Category:{language}'))
        for task in all_tasks:
            if task not in language_tasks:
                yield task


def tasks_titles(*,
                 session: requests.Session,
                 url: str,
                 **params: Union[str, int, bool]) -> Iterator[str]:
    """Yields tasks names for a specified category"""
    while True:
        request = session.get(url, params=params)
        data = request.json()
        yield from map(itemgetter('title'), data['query']['categorymembers'])
        query_continue = data.get('query-continue')
        if query_continue is None:
            return
        params.update(query_continue['categorymembers'])


if __name__ == '__main__':
    tasks = unimplemented_tasks('Python',
                                url=URL,
                                request_params=REQUEST_PARAMETERS)
    print(*tasks, sep='\n')

Current output for Python:

Calendar - for "REAL" programmers
Catmull–Clark subdivision surface
Checkpoint synchronization
Colour pinstripe/Printer
Compile-time calculation
Constrained genericity
Create an object at a given address
Deconvolution/2D+
Factorial base numbers indexing permutations of a collection
Fixed length records
Four is the number of letters in the ...
Function prototype
Hilbert curve
Parametric polymorphism
Pattern matching
Peano curve
Pinstripe/Printer
Rendezvous
Rosetta Code/Find bare lang tags
Start from a main routine
Suffixation of decimal numbers
Video display modes
Zeckendorf arithmetic
\$\endgroup\$
1
\$\begingroup\$

LGTM. Ship it!

There were two minor nits that seemed slightly off:

  1. The use of * in the signature to force kw args is maybe overused, as it covers args that are pretty mandatory.
  2. The print(*tasks, sep='\n') would more naturally be handled by this idiom, which is reusable outside of print(): print('\n'.join(tasks))
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thanks for the review! Yes, keyword-only arguments is still something that I don't have a clear idea about. See my related question: What is the safe way of using keyword-only arguments? I'm accepting the answer but if somebody has something else to propose, feel free to post it. BTW, after I posted this question I came up with a more elegant way to solve the task using mwclient library: link. \$\endgroup\$ – Georgy Nov 28 '19 at 9:18
  • \$\begingroup\$ Well, if you have a function that uses *args and you're adding args, then they will be kw args, and you'll need a * before them. A function with a tediously large number of ordinary args (say four or more) may benefit from *, if you're concerned the caller might confuse the order switching 3rd & 4th with no possibility of an exception alerting him to the trouble. I really like the @Jerry101 answer you accepted, especially the "dangerous" aspect of this example: def delete(base, *, recursive) \$\endgroup\$ – J_H Nov 28 '19 at 20:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.