Basic Trigonometry written in python3
All of your arguments accept *args but don't use it. If someone calls your functions like sin(some_angle), it's going to return None which is confusing. You can simply leave it out, **kwargs still works.

This is more a situation that calls for keyword-only arguments, because each function only has a small fixed number of possible arguments. This leads to better errors if, say, you misspell one of the argument names in a call. If you want to use keyword-only arguments without *args, you can add a single *, before the arguments, like this:
def sin(*, degrees=None, opposite=None, hypotenuse=None):
    if degrees:
       return math.sin(math.radians(degrees))
    elif opposite and hypotenuse:
       return opposite / hypotenuse


Testing for possible arguments should be done with if x is not None instead of if x: getting sin(degrees=0) is totally valid but will return None if you test for degrees instead of degrees is not None!

You're sort of overloading each function based on arguments passed. This makes the functions error-prone to use and possibly confusing. You can call sin(degrees=60, opposite=1, hypotenuse=1) and your program won't complain. You can call sin() and get None.
A possible solution would be to split the first four functions out like this:
def sin_from_degrees(degrees)
def sin_from_edges(*, opposite, hypotenuse)
def cos_from_degrees(degrees)
def cos_from_edges(*, adjacent, hypotenuse)
def tan_from_degrees(degrees)
def tan_from_edges(*, opposite, adjacent)
def opposite_from_sin(*, sin, hypotenuse)
def opposite_from_tan(*, tan, adjacent)
def adjacent_from_cos(*, cos, hypotenuse)
def adjacent_from_tan(*, tan, opposite)
def hypotenuse_from_sin(*, sin, opposite)
def hypotenuse_from_cos(*, cos, adjacent)

Note how none of the arguments are optional any more, and if you try to use a function the wrong way, Python will give you a nice error message explaining exactly what went wrong. I also made degrees a regular argument, because I felt that sin_from_degrees(degrees=degrees) would be a bit redundant and there's no confusion about which argument means what if there's only one, although I can understand wanting the API to be more consistent.

Your last two function is interesting in that it is overloaded to an extent, but not as easy to extricate as the other functions. I would use a namedtuple instead of a dictionary, and definitely signal if pythagorean is called with the wrong arguments.
from typing import NamedTuple

class Triangle(NamedTuple):
    adjacent: float
    opposite: float
    hypotenuse: float

def pythagorean(*, adjacent=None, opposite=None, hypotenuse=None):
    if hypotenuse is not None and opposite is not None and adjacent is None:
        adjacent = math.sqrt(hypotenuse * hypotenuse - opposite * opposite)
    elif adjacent is not None and opposite is not None and hypotenuse is None:
        hypotenuse = math.sqrt(adjacent * adjacent + opposite * opposite)
    elif hypotenuse is not None and adjacent is not None and opposite is None:
        opposite = math.sqrt(hypotenuse * hypotenuse - adjacent * adjacent)
    else:
        raise TypeError(&quot;pythogorean() expects exactly two arguments from 'adjacent', 'opposite', and 'hypotenuse'&quot;)
    return Triangle(adjacent, opposite, hypotenuse)

There is still a bit of duplication (if a is not None and b is not None and c is None: c = math.sqrt(a * a ± b * b) occurs three times) but I'm not sure at the moment how to cleanly refactor that, especially due to the sign change when calculating the hypotenuse. You could make it a bit shorter by using comparator chaining (a is not None is not b) but honestly I think that would be hurt readability and maintainability more than it would help.


Maybe I've gone a bit overboard with suggesting changing things you didn't ask about, but I hope that at least answers your questions!