diff --git a/api/funkwhale_api/contrib/listenbrainz/tasks.py b/api/funkwhale_api/contrib/listenbrainz/tasks.py index a37d60272..a540fd85d 100644 --- a/api/funkwhale_api/contrib/listenbrainz/tasks.py +++ b/api/funkwhale_api/contrib/listenbrainz/tasks.py @@ -57,7 +57,7 @@ def import_listenbrainz_listenings(user, user_name, since): new_ts = max( listens, key=lambda obj: datetime.datetime.fromtimestamp( - obj.listened_at, timezone.utc + obj.listened_at, datetime.timezone.utc ), ) response = client.get_listens(username=user_name, min_ts=new_ts, count=100) @@ -74,7 +74,7 @@ def add_lb_listenings_to_db(listens, user): == "Funkwhale ListenBrainz plugin" and history_models.Listening.objects.filter( creation_date=datetime.datetime.fromtimestamp( - listen.listened_at, timezone.utc + listen.listened_at, datetime.timezone.utc ) ).exists() ): @@ -103,7 +103,7 @@ def add_lb_listenings_to_db(listens, user): user = user fw_listen = history_models.Listening( creation_date=datetime.datetime.fromtimestamp( - listen.listened_at, timezone.utc + listen.listened_at, datetime.timezone.utc ), track=track, actor=user.actor, @@ -125,7 +125,7 @@ def import_listenbrainz_favorites(user, user_name, since): last_sync = min( response["feedback"], key=lambda obj: datetime.datetime.fromtimestamp( - obj["created"], timezone.utc + obj["created"], datetime.timezone.utc ), )["created"] add_lb_feedback_to_db(response["feedback"], user) @@ -149,7 +149,7 @@ def add_lb_feedback_to_db(feedbacks, user): favorites_models.TrackFavorite.objects.get_or_create( actor=user.actor, creation_date=datetime.datetime.fromtimestamp( - feedback["created"], timezone.utc + feedback["created"], datetime.timezone.utc ), track=track, source="Listenbrainz", diff --git a/api/funkwhale_api/federation/factories.py b/api/funkwhale_api/federation/factories.py index bc285006b..3cc9f3fcb 100644 --- a/api/funkwhale_api/federation/factories.py +++ b/api/funkwhale_api/federation/factories.py @@ -128,11 +128,6 @@ class ActorFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): class Meta: model = models.Actor - class Params: - with_real_keys = factory.Trait( - keys=factory.LazyFunction(keys.get_key_pair), - ) - @factory.post_generation def local(self, create, extracted, **kwargs): if not extracted and not kwargs: @@ -153,6 +148,26 @@ class ActorFactory(NoUpdateOnCreate, factory.django.DjangoModelFactory): extracted.actor = self extracted.save(update_fields=["user"]) else: + user = UserFactory(actor=self, **kwargs) + user.actor = self + user.save() + + @factory.post_generation + def user(self, create, extracted, **kwargs): + """ + Handle the creation or assignment of the related user instance. + If `actor__user` is passed, it will be linked; otherwise, no user is created. + """ + if not create: + return + + if extracted: # If a User instance is provided + extracted.actor = self + extracted.save(update_fields=["actor"]) + elif kwargs: + from funkwhale_api.users.factories import UserFactory + + # Create a User linked to this Actor self.user = UserFactory(actor=self, **kwargs) diff --git a/api/funkwhale_api/federation/signing.py b/api/funkwhale_api/federation/signing.py index 1706e5215..eec236fdf 100644 --- a/api/funkwhale_api/federation/signing.py +++ b/api/funkwhale_api/federation/signing.py @@ -30,7 +30,7 @@ def verify_date(raw_date): ts = parse_http_date(raw_date) except ValueError as e: raise forms.ValidationError(str(e)) - dt = datetime.datetime.utcfromtimestamp(ts) + dt = datetime.datetime.fromtimestamp(ts, datetime.timezone.utc) dt = dt.replace(tzinfo=ZoneInfo("UTC")) delta = datetime.timedelta(seconds=DATE_HEADER_VALID_FOR) now = timezone.now() diff --git a/api/funkwhale_api/music/filters.py b/api/funkwhale_api/music/filters.py index 1f0eedd76..982cfe72f 100644 --- a/api/funkwhale_api/music/filters.py +++ b/api/funkwhale_api/music/filters.py @@ -16,7 +16,7 @@ from . import models, utils def filter_tags(queryset, name, value): non_empty_tags = [v.lower() for v in value if v] for tag in non_empty_tags: - queryset = queryset.filter(tagged_items__tag__name=tag).distinct() + queryset = queryset.filter(tagged_items__tag__name__iexact=tag).distinct() return queryset diff --git a/api/funkwhale_api/music/management/commands/import_files.py b/api/funkwhale_api/music/management/commands/import_files.py index 2435b6fe3..f1c1d4331 100644 --- a/api/funkwhale_api/music/management/commands/import_files.py +++ b/api/funkwhale_api/music/management/commands/import_files.py @@ -630,7 +630,7 @@ def process_load_queue(stdout, **kwargs): for path, event in batched_events.copy().items(): if time.time() - event["time"] <= flush_delay: continue - now = datetime.datetime.utcnow() + now = datetime.datetime.now(datetime.timezone.utc) stdout.write( "{} -- Processing {}:{}...\n".format( now.strftime("%Y/%m/%d %H:%M:%S"), event["type"], event["path"] diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py index a8425a313..9fc7e9e72 100644 --- a/api/funkwhale_api/music/models.py +++ b/api/funkwhale_api/music/models.py @@ -7,6 +7,7 @@ import urllib.parse import uuid import arrow +import slugify from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation from django.contrib.postgres.indexes import GinIndex @@ -775,6 +776,9 @@ TRACK_FILE_IMPORT_STATUS_CHOICES = ( def get_file_path(instance, filename): + # Convert unicode characters in name to ASCII characters. + filename = slugify.slugify(filename, ok=slugify.SLUG_OK + ".", only_ascii=True) + if isinstance(instance, UploadVersion): return common_utils.ChunkedPath("transcoded")(instance, filename) diff --git a/api/funkwhale_api/music/tasks.py b/api/funkwhale_api/music/tasks.py index 0dfcb6dfd..cc0fcaaf2 100644 --- a/api/funkwhale_api/music/tasks.py +++ b/api/funkwhale_api/music/tasks.py @@ -176,7 +176,7 @@ def fail_import(upload, error_code, detail=None, **fields): upload.import_metadata, "funkwhale", "config", "broadcast", default=True ) if broadcast: - signals.upload_import_status_updated.send( + signals.upload_import_status_updated.send_robust( old_status=old_status, new_status=upload.import_status, upload=upload, @@ -297,7 +297,7 @@ def process_upload(upload, update_denormalization=True): update_fields=["import_details", "import_status", "import_date", "track"] ) if broadcast: - signals.upload_import_status_updated.send( + signals.upload_import_status_updated.send_robust( old_status=old_status, new_status=upload.import_status, upload=upload, @@ -341,7 +341,7 @@ def process_upload(upload, update_denormalization=True): ) if broadcast: - signals.upload_import_status_updated.send( + signals.upload_import_status_updated.send_robust( old_status=old_status, new_status=upload.import_status, upload=upload, @@ -993,7 +993,7 @@ def albums_set_tags_from_tracks(ids=None, dry_run=False): data = tags_tasks.get_tags_from_foreign_key( ids=qs, foreign_key_model=models.Track, - foreign_key_attr="album", + foreign_key_attr="albums", ) logger.info("Found automatic tags for %s albums…", len(data)) if dry_run: diff --git a/api/funkwhale_api/subsonic/views.py b/api/funkwhale_api/subsonic/views.py index e3f43cc6e..7b5712de9 100644 --- a/api/funkwhale_api/subsonic/views.py +++ b/api/funkwhale_api/subsonic/views.py @@ -419,12 +419,12 @@ class SubsonicViewSet(viewsets.GenericViewSet): queryset = ( queryset.playable_by(actor) .filter( - Q(tagged_items__tag__name=genre) - | Q(artist_credit__artist__tagged_items__tag__name=genre) + Q(tagged_items__tag__name__iexact=genre) + | Q(artist_credit__artist__tagged_items__tag__name__iexact=genre) | Q( - artist_credit__albums__artist_credit__artist__tagged_items__tag__name=genre + artist_credit__albums__artist_credit__artist__tagged_items__tag__name__iexact=genre ) - | Q(artist_credit__albums__tagged_items__tag__name=genre) + | Q(artist_credit__albums__tagged_items__tag__name__iexact=genre) ) .prefetch_related("uploads") .distinct() @@ -485,8 +485,8 @@ class SubsonicViewSet(viewsets.GenericViewSet): elif type == "byGenre" and data.get("genre"): genre = data.get("genre") queryset = queryset.filter( - Q(tagged_items__tag__name=genre) - | Q(artist_credit__artist__tagged_items__tag__name=genre) + Q(tagged_items__tag__name__iexact=genre) + | Q(artist_credit__artist__tagged_items__tag__name__iexact=genre) ) elif type == "byYear": try: diff --git a/api/funkwhale_api/tags/migrations/0003_tag_mbid_alter_tag_name.py b/api/funkwhale_api/tags/migrations/0003_tag_mbid_alter_tag_name.py index f43b2f36a..2e7019d09 100644 --- a/api/funkwhale_api/tags/migrations/0003_tag_mbid_alter_tag_name.py +++ b/api/funkwhale_api/tags/migrations/0003_tag_mbid_alter_tag_name.py @@ -11,12 +11,6 @@ class Migration(migrations.Migration): ] operations = [ - CreateCollation( - "case_insensitive", - provider="icu", - locale="und-u-ks-level2", - deterministic=False, - ), migrations.AddField( model_name="tag", name="mbid", @@ -25,8 +19,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name="tag", name="name", - field=models.CharField( - db_collation="case_insensitive", max_length=100, unique=True - ), + field=models.CharField(max_length=100, unique=True), ), ] diff --git a/api/funkwhale_api/tags/models.py b/api/funkwhale_api/tags/models.py index bea45c9a8..f87681548 100644 --- a/api/funkwhale_api/tags/models.py +++ b/api/funkwhale_api/tags/models.py @@ -12,7 +12,8 @@ TAG_REGEX = re.compile(r"^((\w+)([\d_]*))$") class Tag(models.Model): name = models.CharField( - max_length=100, unique=True, db_collation="case_insensitive" + max_length=100, + unique=True, ) mbid = models.UUIDField(null=True, db_index=True, blank=True, unique=True) creation_date = models.DateTimeField(default=timezone.now) diff --git a/api/funkwhale_api/tags/tasks.py b/api/funkwhale_api/tags/tasks.py index 8c5bcbd46..ba4fc4e02 100644 --- a/api/funkwhale_api/tags/tasks.py +++ b/api/funkwhale_api/tags/tasks.py @@ -24,10 +24,10 @@ def get_tags_from_foreign_key( objs = foreign_key_model.objects.filter( **{f"artist_credit__{foreign_key_attr}__pk__in": ids} ).order_by("-id") - objs = objs.only("id", f"artist_credit__{foreign_key_attr}_id").prefetch_related( + objs = objs.only("id", f"artist_credit__{foreign_key_attr}__id").prefetch_related( tagged_items_attr ) - for obj in objs.iterator(): + for obj in objs.iterator(chunk_size=1000): for ac in obj.artist_credit.all(): # loop on all objects, store the objs tags + counter on the corresponding foreign key row_data = data.setdefault( diff --git a/api/funkwhale_api/users/migrations/0026_accesstoken_token_checksum_refreshtoken_token_family_and_more.py b/api/funkwhale_api/users/migrations/0026_accesstoken_token_checksum_refreshtoken_token_family_and_more.py new file mode 100644 index 000000000..d5d23dd38 --- /dev/null +++ b/api/funkwhale_api/users/migrations/0026_accesstoken_token_checksum_refreshtoken_token_family_and_more.py @@ -0,0 +1,55 @@ +# Generated by Django 5.1.5 on 2025-01-15 17:10 + +import oauth2_provider.models +from django.db import migrations, models + +import oauth2_provider.models +from django.db import migrations, models +from oauth2_provider.settings import oauth2_settings + +# see https://github.com/jazzband/django-oauth-toolkit/blob/master/oauth2_provider/migrations/0012_add_token_checksum.py + + +def forwards_func(apps, schema_editor): + """ + Forward migration touches every "old" accesstoken.token which will cause the checksum to be computed. + """ + AccessToken = apps.get_model(oauth2_settings.ACCESS_TOKEN_MODEL) + accesstokens = AccessToken._default_manager.iterator() + for accesstoken in accesstokens: + accesstoken.save(update_fields=["token_checksum"]) + + +class Migration(migrations.Migration): + dependencies = [ + ("users", "0025_application_allowed_origins_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="accesstoken", + name="token_checksum", + field=oauth2_provider.models.TokenChecksumField( + blank=True, null=True, max_length=64 + ), + preserve_default=False, + ), + migrations.AddField( + model_name="refreshtoken", + name="token_family", + field=models.UUIDField(blank=True, editable=False, null=True), + ), + migrations.AlterField( + model_name="accesstoken", + name="token", + field=models.TextField(), + ), + migrations.RunPython(forwards_func, migrations.RunPython.noop), + migrations.AlterField( + model_name="accesstoken", + name="token_checksum", + field=oauth2_provider.models.TokenChecksumField( + blank=False, max_length=64, db_index=True, unique=True + ), + ), + ] diff --git a/api/funkwhale_api/users/models.py b/api/funkwhale_api/users/models.py index 20a920ab4..d4338a147 100644 --- a/api/funkwhale_api/users/models.py +++ b/api/funkwhale_api/users/models.py @@ -455,7 +455,11 @@ def create_actor(user, **kwargs): args["private_key"] = private.decode("utf-8") args["public_key"] = public.decode("utf-8") - return federation_models.Actor.objects.create(user=user, **args) + actor = federation_models.Actor.objects.create(**args) + user.actor = actor + user.save() + + return actor def create_user_libraries(user): diff --git a/api/poetry.lock b/api/poetry.lock index 25c8a93a3..b0101571d 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -544,6 +544,7 @@ description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" groups = ["main"] +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -945,48 +946,52 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "41.0.7" +version = "44.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false -python-versions = ">=3.7" +python-versions = "!=3.9.0,!=3.9.1,>=3.7" groups = ["main"] files = [ - {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:3c78451b78313fa81607fa1b3f1ae0a5ddd8014c38a02d9db0616133987b9cdf"}, - {file = "cryptography-41.0.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:928258ba5d6f8ae644e764d0f996d61a8777559f72dfeb2eea7e2fe0ad6e782d"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a1b41bc97f1ad230a41657d9155113c7521953869ae57ac39ac7f1bb471469a"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:841df4caa01008bad253bce2a6f7b47f86dc9f08df4b433c404def869f590a15"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5429ec739a29df2e29e15d082f1d9ad683701f0ec7709ca479b3ff2708dae65a"}, - {file = "cryptography-41.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:43f2552a2378b44869fe8827aa19e69512e3245a219104438692385b0ee119d1"}, - {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:af03b32695b24d85a75d40e1ba39ffe7db7ffcb099fe507b39fd41a565f1b157"}, - {file = "cryptography-41.0.7-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:49f0805fc0b2ac8d4882dd52f4a3b935b210935d500b6b805f321addc8177406"}, - {file = "cryptography-41.0.7-cp37-abi3-win32.whl", hash = "sha256:f983596065a18a2183e7f79ab3fd4c475205b839e02cbc0efbbf9666c4b3083d"}, - {file = "cryptography-41.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:90452ba79b8788fa380dfb587cca692976ef4e757b194b093d845e8d99f612f2"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:079b85658ea2f59c4f43b70f8119a52414cdb7be34da5d019a77bf96d473b960"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b640981bf64a3e978a56167594a0e97db71c89a479da8e175d8bb5be5178c003"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e3114da6d7f95d2dee7d3f4eec16dacff819740bbab931aff8648cb13c5ff5e7"}, - {file = "cryptography-41.0.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d5ec85080cce7b0513cfd233914eb8b7bbd0633f1d1703aa28d1dd5a72f678ec"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a698cb1dac82c35fcf8fe3417a3aaba97de16a01ac914b89a0889d364d2f6be"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:37a138589b12069efb424220bf78eac59ca68b95696fc622b6ccc1c0a197204a"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:68a2dec79deebc5d26d617bfdf6e8aab065a4f34934b22d3b5010df3ba36612c"}, - {file = "cryptography-41.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:09616eeaef406f99046553b8a40fbf8b1e70795a91885ba4c96a70793de5504a"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:48a0476626da912a44cc078f9893f292f0b3e4c739caf289268168d8f4702a39"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c7f3201ec47d5207841402594f1d7950879ef890c0c495052fa62f58283fde1a"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c5ca78485a255e03c32b513f8c2bc39fedb7f5c5f8535545bdc223a03b24f248"}, - {file = "cryptography-41.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6c391c021ab1f7a82da5d8d0b3cee2f4b2c455ec86c8aebbc84837a631ff309"}, - {file = "cryptography-41.0.7.tar.gz", hash = "sha256:13f93ce9bea8016c253b34afc6bd6a75993e5c40672ed5405a9c832f0d4a00bc"}, + {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, + {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, + {file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"}, + {file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, + {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, + {file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"}, + {file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"}, ] [package.dependencies] -cffi = ">=1.12" +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] -nox = ["nox"] -pep8test = ["black", "check-sdist", "mypy", "ruff"] -sdist = ["build"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] [[package]] @@ -1105,18 +1110,18 @@ with-social = ["django-allauth[socialaccount] (>=64.0.0)"] [[package]] name = "django" -version = "4.2.18" +version = "5.1.5" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "Django-4.2.18-py3-none-any.whl", hash = "sha256:ba52eff7e228f1c775d5b0db2ba53d8c49d2f8bfe6ca0234df6b7dd12fb25b19"}, - {file = "Django-4.2.18.tar.gz", hash = "sha256:52ae8eacf635617c0f13b44f749e5ea13dc34262819b2cc8c8636abb08d82c4b"}, + {file = "Django-5.1.5-py3-none-any.whl", hash = "sha256:c46eb936111fffe6ec4bc9930035524a8be98ec2f74d8a0ff351226a3e52f459"}, + {file = "Django-5.1.5.tar.gz", hash = "sha256:19bbca786df50b9eca23cee79d495facf55c8f5c54c529d9bf1fe7b5ea086af3"}, ] [package.dependencies] -asgiref = ">=3.6.0,<4" +asgiref = ">=3.8.1,<4" sqlparse = ">=0.3.1" tzdata = {version = "*", markers = "sys_platform == \"win32\""} @@ -1126,20 +1131,21 @@ bcrypt = ["bcrypt"] [[package]] name = "django-allauth" -version = "0.63.6" +version = "65.3.1" description = "Integrated set of Django applications addressing authentication, registration, account management as well as 3rd party (social) account authentication." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "django_allauth-0.63.6.tar.gz", hash = "sha256:f15f49defb09e0604dad5214e53a69a1f723cb03176bb115c8930bcd19b91749"}, + {file = "django_allauth-65.3.1.tar.gz", hash = "sha256:e02e951b71a2753a746459f2efa114c7c72bf2cef6887dbe8607a577c0350587"}, ] [package.dependencies] -Django = ">=3.2" +asgiref = ">=3.8.1" +Django = ">=4.2.16" [package.extras] -mfa = ["qrcode (>=7.0.0)"] +mfa = ["fido2 (>=1.1.2)", "qrcode (>=7.0.0)"] openid = ["python3-openid (>=3.0.8)"] saml = ["python3-saml (>=1.15.0,<2.0.0)"] socialaccount = ["pyjwt[crypto] (>=1.7)", "requests (>=2.0.0)", "requests-oauthlib (>=0.3.0)"] @@ -1147,18 +1153,18 @@ steam = ["python3-openid (>=3.0.8)"] [[package]] name = "django-auth-ldap" -version = "4.8.0" +version = "5.1.0" description = "Django LDAP authentication backend" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "django-auth-ldap-4.8.0.tar.gz", hash = "sha256:604250938ddc9fda619f247c7a59b0b2f06e53a7d3f46a156f28aa30dd71a738"}, - {file = "django_auth_ldap-4.8.0-py3-none-any.whl", hash = "sha256:4b4b944f3c28bce362f33fb6e8db68429ed8fd8f12f0c0c4b1a4344a7ef225ce"}, + {file = "django_auth_ldap-5.1.0-py3-none-any.whl", hash = "sha256:a5f7bdb54b2ab80e4e9eb080cd3e06e89e4c9d2d534ddb39b66cd970dd6d3536"}, + {file = "django_auth_ldap-5.1.0.tar.gz", hash = "sha256:9c607e8d9c53cf2a0ccafbe0acfc33eb1d1fd474c46ec52d30aee0dca1da9668"}, ] [package.dependencies] -Django = ">=3.2" +Django = ">=4.2" python-ldap = ">=3.1" [[package]] @@ -1195,14 +1201,14 @@ redis = ">=3.0.0" [[package]] name = "django-cleanup" -version = "8.1.0" +version = "9.0.0" description = "Deletes old files." optional = false -python-versions = "*" +python-versions = ">=3.8" groups = ["main"] files = [ - {file = "django-cleanup-8.1.0.tar.gz", hash = "sha256:70df905076a44e7a111b31198199af633dee08876e199e6dce36ca8dd6b8b10f"}, - {file = "django_cleanup-8.1.0-py2.py3-none-any.whl", hash = "sha256:7903873ea73b3f7e61e055340d27dba49b70634f60c87a573ad748e172836458"}, + {file = "django_cleanup-9.0.0-py3-none-any.whl", hash = "sha256:19f8b0e830233f9f0f683b17181f414672a0f48afe3ea3cc80ba47ae40ad880c"}, + {file = "django_cleanup-9.0.0.tar.gz", hash = "sha256:bb9fb560aaf62959c81e31fa40885c36bbd5854d5aa21b90df2c7e4ba633531e"}, ] [[package]] @@ -1238,14 +1244,14 @@ coverage = "*" [[package]] name = "django-debug-toolbar" -version = "4.4.6" +version = "5.0.1" description = "A configurable set of panels that display various debug information about the current request/response." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "django_debug_toolbar-4.4.6-py3-none-any.whl", hash = "sha256:3beb671c9ec44ffb817fad2780667f172bd1c067dbcabad6268ce39a81335f45"}, - {file = "django_debug_toolbar-4.4.6.tar.gz", hash = "sha256:36e421cb908c2f0675e07f9f41e3d1d8618dc386392ec82d23bcfcd5d29c7044"}, + {file = "django_debug_toolbar-5.0.1-py3-none-any.whl", hash = "sha256:7456cc2e951db37dab335686db7803c4a0ecb6736d120705f6668db9548bf49f"}, + {file = "django_debug_toolbar-5.0.1.tar.gz", hash = "sha256:296f6f18a80710e84fbb8361538ae5ec522a75ebe9ab67db34bcf1026cbeb420"}, ] [package.dependencies] @@ -1317,21 +1323,20 @@ Django = ">=4.2" [[package]] name = "django-oauth-toolkit" -version = "2.4.0" +version = "3.0.1" description = "OAuth2 Provider for Django" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "django_oauth_toolkit-2.4.0-py3-none-any.whl", hash = "sha256:4931d6bf64b6aee32a42f989f218769d1876f3daa53c6bf883d8ab793fb302ee"}, - {file = "django_oauth_toolkit-2.4.0.tar.gz", hash = "sha256:8975eaf697413a8d54208ee068bc5ad6d1ed76f1df84e4882fbb25e7e6966e1b"}, + {file = "django_oauth_toolkit-3.0.1-py3-none-any.whl", hash = "sha256:3ef00b062a284f2031b0732b32dc899e3bbf0eac221bbb1cffcb50b8932e55ed"}, + {file = "django_oauth_toolkit-3.0.1.tar.gz", hash = "sha256:7200e4a9fb229b145a6d808cbf0423b6d69a87f68557437733eec3c0cf71db02"}, ] [package.dependencies] -django = ">=3.2,<4.0.0 || >4.0.0" -jwcrypto = ">=0.8.0" -oauthlib = ">=3.1.0" -pytz = ">=2024.1" +django = ">=4.2" +jwcrypto = ">=1.5.0" +oauthlib = ">=3.2.2" requests = ">=2.13.0" [[package]] @@ -1485,18 +1490,19 @@ doc = ["Sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"] [[package]] name = "faker" -version = "23.3.0" +version = "33.3.1" description = "Faker is a Python package that generates fake data for you." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "Faker-23.3.0-py3-none-any.whl", hash = "sha256:117ce1a2805c1bc5ca753b3dc6f9d567732893b2294b827d3164261ee8f20267"}, - {file = "Faker-23.3.0.tar.gz", hash = "sha256:458d93580de34403a8dec1e8d5e6be2fee96c4deca63b95d71df7a6a80a690de"}, + {file = "Faker-33.3.1-py3-none-any.whl", hash = "sha256:ac4cf2f967ce02c898efa50651c43180bd658a7707cfd676fcc5410ad1482c03"}, + {file = "faker-33.3.1.tar.gz", hash = "sha256:49dde3b06a5602177bc2ad013149b6f60a290b7154539180d37b6f876ae79b20"}, ] [package.dependencies] python-dateutil = ">=2.4" +typing-extensions = "*" [[package]] name = "feedparser" @@ -1515,20 +1521,20 @@ sgmllib3k = "*" [[package]] name = "flake8" -version = "3.9.2" +version = "7.1.1" description = "the modular source code checker: pep8 pyflakes and co" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +python-versions = ">=3.8.1" groups = ["dev"] files = [ - {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, - {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, + {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"}, + {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"}, ] [package.dependencies] -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.7.0,<2.8.0" -pyflakes = ">=2.3.0,<2.4.0" +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.12.0,<2.13.0" +pyflakes = ">=3.2.0,<3.3.0" [[package]] name = "frozendict" @@ -1695,23 +1701,24 @@ files = [ [[package]] name = "gunicorn" -version = "21.2.0" +version = "23.0.0" description = "WSGI HTTP Server for UNIX" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" groups = ["main"] files = [ - {file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"}, - {file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"}, + {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, + {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, ] [package.dependencies] packaging = "*" [package.extras] -eventlet = ["eventlet (>=0.24.1)"] +eventlet = ["eventlet (>=0.24.1,!=0.36.0)"] gevent = ["gevent (>=1.4.0)"] setproctitle = ["setproctitle"] +testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] tornado = ["tornado (>=0.2)"] [[package]] @@ -2284,14 +2291,14 @@ traitlets = "*" [[package]] name = "mccabe" -version = "0.6.1" +version = "0.7.0" description = "McCabe checker, plugin for flake8" optional = false -python-versions = "*" +python-versions = ">=3.6" groups = ["dev"] files = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] [[package]] @@ -3069,26 +3076,26 @@ pyasn1 = ">=0.4.6,<0.7.0" [[package]] name = "pycodestyle" -version = "2.7.0" +version = "2.12.1" description = "Python style guide checker" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, - {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, + {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, + {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, ] [[package]] name = "pycountry" -version = "23.12.11" +version = "24.6.1" description = "ISO country, subdivision, language, currency and script definitions and their translations" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "pycountry-23.12.11-py3-none-any.whl", hash = "sha256:2ff91cff4f40ff61086e773d61e72005fe95de4a57bfc765509db05695dc50ab"}, - {file = "pycountry-23.12.11.tar.gz", hash = "sha256:00569d82eaefbc6a490a311bfa84a9c571cff9ddbf8b0a4f4e7b4f868b4ad925"}, + {file = "pycountry-24.6.1-py3-none-any.whl", hash = "sha256:f1a4fb391cd7214f8eefd39556d740adcc233c778a27f8942c8dca351d6ce06f"}, + {file = "pycountry-24.6.1.tar.gz", hash = "sha256:b61b3faccea67f87d10c1f2b0fc0be714409e8fcdcc1315613174f6466c10221"}, ] [[package]] @@ -3098,6 +3105,7 @@ description = "C parser in Python" optional = false python-versions = ">=3.8" groups = ["main"] +markers = "platform_python_implementation != \"PyPy\"" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, @@ -3123,14 +3131,14 @@ dev = ["black", "build", "flake8", "flake8-black", "isort", "jupyter-console", " [[package]] name = "pyflakes" -version = "2.3.1" +version = "3.2.0" description = "passive checker of Python programs" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, - {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, + {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, + {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, ] [[package]] @@ -3299,22 +3307,22 @@ testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] name = "pytest-cov" -version = "4.1.0" +version = "6.0.0" description = "Pytest plugin for measuring coverage." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, + {file = "pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0"}, + {file = "pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35"}, ] [package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} +coverage = {version = ">=7.5", extras = ["toml"]} pytest = ">=4.6" [package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-django" @@ -3480,18 +3488,6 @@ files = [ {file = "python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3"}, ] -[[package]] -name = "pytz" -version = "2024.2" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, - {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, -] - [[package]] name = "pyyaml" version = "6.0.2" @@ -3996,22 +3992,23 @@ test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis [[package]] name = "sentry-sdk" -version = "1.45.1" +version = "2.20.0" description = "Python client for Sentry (https://sentry.io)" optional = false -python-versions = "*" +python-versions = ">=3.6" groups = ["main"] files = [ - {file = "sentry_sdk-1.45.1-py2.py3-none-any.whl", hash = "sha256:608887855ccfe39032bfd03936e3a1c4f4fc99b3a4ac49ced54a4220de61c9c1"}, - {file = "sentry_sdk-1.45.1.tar.gz", hash = "sha256:a16c997c0f4e3df63c0fc5e4207ccb1ab37900433e0f72fef88315d317829a26"}, + {file = "sentry_sdk-2.20.0-py2.py3-none-any.whl", hash = "sha256:c359a1edf950eb5e80cffd7d9111f3dbeef57994cb4415df37d39fda2cf22364"}, + {file = "sentry_sdk-2.20.0.tar.gz", hash = "sha256:afa82713a92facf847df3c6f63cec71eb488d826a50965def3d7722aa6f0fdab"}, ] [package.dependencies] certifi = "*" -urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} +urllib3 = ">=1.26.11" [package.extras] aiohttp = ["aiohttp (>=3.5)"] +anthropic = ["anthropic (>=0.16)"] arq = ["arq (>=0.23)"] asyncpg = ["asyncpg (>=0.23)"] beam = ["apache-beam (>=2.12)"] @@ -4024,14 +4021,20 @@ django = ["django (>=1.8)"] falcon = ["falcon (>=1.4)"] fastapi = ["fastapi (>=0.79.0)"] flask = ["blinker (>=1.1)", "flask (>=0.11)", "markupsafe"] -grpcio = ["grpcio (>=1.21.1)"] +grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"] +http2 = ["httpcore[http2] (==1.*)"] httpx = ["httpx (>=0.16.0)"] huey = ["huey (>=2)"] +huggingface-hub = ["huggingface_hub (>=0.22)"] +langchain = ["langchain (>=0.0.210)"] +launchdarkly = ["launchdarkly-server-sdk (>=9.8.0)"] +litestar = ["litestar (>=2.0.0)"] loguru = ["loguru (>=0.5)"] openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] +openfeature = ["openfeature-sdk (>=0.7.1)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] -opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] -pure-eval = ["asttokens", "executing", "pure-eval"] +opentelemetry-experimental = ["opentelemetry-distro"] +pure-eval = ["asttokens", "executing", "pure_eval"] pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] @@ -4040,7 +4043,8 @@ sanic = ["sanic (>=0.8)"] sqlalchemy = ["sqlalchemy (>=1.2)"] starlette = ["starlette (>=0.19.1)"] starlite = ["starlite (>=1.48)"] -tornado = ["tornado (>=5)"] +tornado = ["tornado (>=6)"] +unleash = ["UnleashClient (>=6.0.1)"] [[package]] name = "service-identity" @@ -4301,14 +4305,14 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0, [[package]] name = "troi" -version = "2024.12.4.0" +version = "2025.1.10.0" description = "ListenBrainz' empathic music recommendation/playlisting engine" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "troi-2024.12.4.0-py3-none-any.whl", hash = "sha256:9fcd2b3acb2612e1cd5500830c5075c5699019285b5974bdf905323761558379"}, - {file = "troi-2024.12.4.0.tar.gz", hash = "sha256:77c62696583aac7b6cd17c4f63a8e74008ba716769e9a4ff4133c0788e688a8f"}, + {file = "troi-2025.1.10.0-py3-none-any.whl", hash = "sha256:80693228ab924d7c9e8f1c7dbbe4971b770e8ccc027ba3a35fbedf752b5a12f3"}, + {file = "troi-2025.1.10.0.tar.gz", hash = "sha256:c595365282ebe4bf6cebd0fbf5a27b051c96dff603d4b9719f333d82459d898e"}, ] [package.dependencies] @@ -4431,7 +4435,6 @@ files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] -markers = {dev = "python_version < \"3.12\""} [[package]] name = "tzdata" @@ -4685,47 +4688,42 @@ files = [ [[package]] name = "watchdog" -version = "4.0.2" +version = "6.0.0" description = "Filesystem events monitoring" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22"}, - {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1"}, - {file = "watchdog-4.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503"}, - {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930"}, - {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b"}, - {file = "watchdog-4.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef"}, - {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a"}, - {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29"}, - {file = "watchdog-4.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a"}, - {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd67c7df93eb58f360c43802acc945fa8da70c675b6fa37a241e17ca698ca49b"}, - {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcfd02377be80ef3b6bc4ce481ef3959640458d6feaae0bd43dd90a43da90a7d"}, - {file = "watchdog-4.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:980b71510f59c884d684b3663d46e7a14b457c9611c481e5cef08f4dd022eed7"}, - {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa160781cafff2719b663c8a506156e9289d111d80f3387cf3af49cedee1f040"}, - {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f6ee8dedd255087bc7fe82adf046f0b75479b989185fb0bdf9a98b612170eac7"}, - {file = "watchdog-4.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b4359067d30d5b864e09c8597b112fe0a0a59321a0f331498b013fb097406b4"}, - {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:770eef5372f146997638d737c9a3c597a3b41037cfbc5c41538fc27c09c3a3f9"}, - {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eeea812f38536a0aa859972d50c76e37f4456474b02bd93674d1947cf1e39578"}, - {file = "watchdog-4.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b2c45f6e1e57ebb4687690c05bc3a2c1fb6ab260550c4290b8abb1335e0fd08b"}, - {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa"}, - {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3"}, - {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c100d09ac72a8a08ddbf0629ddfa0b8ee41740f9051429baa8e31bb903ad7508"}, - {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f5315a8c8dd6dd9425b974515081fc0aadca1d1d61e078d2246509fd756141ee"}, - {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2d468028a77b42cc685ed694a7a550a8d1771bb05193ba7b24006b8241a571a1"}, - {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f15edcae3830ff20e55d1f4e743e92970c847bcddc8b7509bcd172aa04de506e"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757"}, - {file = "watchdog-4.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8"}, - {file = "watchdog-4.0.2-py3-none-win32.whl", hash = "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19"}, - {file = "watchdog-4.0.2-py3-none-win_amd64.whl", hash = "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b"}, - {file = "watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c"}, - {file = "watchdog-4.0.2.tar.gz", hash = "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, + {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, + {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, + {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, + {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, ] [package.extras] @@ -5076,4 +5074,4 @@ typesense = ["typesense"] [metadata] lock-version = "2.1" python-versions = "^3.10,<3.14" -content-hash = "81f2e39521ecc944870e2465090d8b0debc867cb1ce6e621b653feb360f6222a" +content-hash = "5338d2d4fed2085b1c581613a567a79d3b96c602ea0dbc6ef9a882dd0efac036" diff --git a/api/pyproject.toml b/api/pyproject.toml index 55be7bd94..a7f67fd6a 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -29,16 +29,16 @@ python = "^3.10,<3.14" # Django dj-rest-auth = "7.0.1" -django = "4.2.18" -django-allauth = "0.63.6" +django = "5.1.5" +django-allauth = "65.3.1" django-cache-memoize = "0.2.1" django-cacheops = "==7.1" -django-cleanup = "==8.1.0" +django-cleanup = "==9.0.0" django-cors-headers = "==4.6.0" django-dynamic-preferences = "==1.17.0" django-environ = "==0.12.0" django-filter = "==24.3" -django-oauth-toolkit = "2.4.0" +django-oauth-toolkit = "3.0.1" django-redis = "==5.4.0" django-storages = "==1.14.4" django-versatileimagefield = "==3.1" @@ -50,7 +50,7 @@ psycopg2-binary = "==2.9.10" redis = "==5.2.1" # Django LDAP -django-auth-ldap = "==4.8.0" +django-auth-ldap = "==5.1.0" python-ldap = "==3.4.4" # Channels @@ -62,7 +62,7 @@ kombu = "5.4.2" celery = "5.4.0" # Deployment -gunicorn = "==21.2.0" +gunicorn = "==23.0.0" uvicorn = { version = "==0.34.0", extras = ["standard"] } # Libs @@ -72,7 +72,7 @@ backports-zoneinfo = { version = "==0.2.1", python = "<3.9" } bleach = "==6.2.0" boto3 = "==1.35.99" click = "==8.1.8" -cryptography = "==41.0.7" +cryptography = "==44.0.0" defusedxml = "0.7.1" feedparser = "==6.0.11" python-ffmpeg = "==2.0.12" @@ -84,12 +84,12 @@ pyld = "==2.0.4" python-magic = "==0.4.27" requests = "==2.32.3" requests-http-message-signatures = "==0.3.1" -sentry-sdk = "==1.45.1" -watchdog = "==4.0.2" -troi = "==2024.12.4.0" +sentry-sdk = "==2.20.0" +watchdog = "==6.0.0" +troi = "==2025.1.10.0" lb-matching-tools = "==2024.1.30.1" unidecode = "==1.3.8" -pycountry = "23.12.11" +pycountry = "24.6.1" # Typesense typesense = { version = "==0.21.0", optional = true } @@ -107,15 +107,15 @@ black = "==24.10.0" coverage = { version = "==7.6.10", extras = ["toml"] } debugpy = "==1.8.11" django-coverage-plugin = "==3.1.0" -django-debug-toolbar = "==4.4.6" +django-debug-toolbar = "==5.0.1" factory-boy = "==3.3.1" -faker = "==23.3.0" -flake8 = "==3.9.2" +faker = "==33.3.1" +flake8 = "==7.1.1" ipdb = "==0.13.13" pytest = "==8.3.4" pytest-asyncio = "==0.25.2" prompt-toolkit = "==3.0.48" -pytest-cov = "==4.1.0" +pytest-cov = "==6.0.0" pytest-django = "==4.9.0" pytest-env = "==1.1.5" pytest-mock = "==3.14.0" diff --git a/api/tests/cli/test_main.py b/api/tests/cli/test_main.py index a0eb1ead4..1dfca233c 100644 --- a/api/tests/cli/test_main.py +++ b/api/tests/cli/test_main.py @@ -1,115 +1,115 @@ import pytest from click.testing import CliRunner -from funkwhale_api.cli import library, main +from funkwhale_api.cli import library, main, users @pytest.mark.parametrize( "cmd, args, handlers", [ - # ( - # ("users", "create"), - # ( - # "--username", - # "testuser", - # "--password", - # "testpassword", - # "--email", - # "test@hello.com", - # "--upload-quota", - # "35", - # "--permission", - # "library", - # "--permission", - # "moderation", - # "--staff", - # "--superuser", - # ), - # [ - # ( - # users, - # "handler_create_user", - # { - # "username": "testuser", - # "password": "testpassword", - # "email": "test@hello.com", - # "upload_quota": 35, - # "permissions": ("library", "moderation"), - # "is_staff": True, - # "is_superuser": True, - # }, - # ) - # ], - # ), - # ( - # ("users", "rm"), - # ("testuser1", "testuser2", "--no-input"), - # [ - # ( - # users, - # "handler_delete_user", - # {"usernames": ("testuser1", "testuser2"), "soft": True}, - # ) - # ], - # ), - # ( - # ("users", "rm"), - # ( - # "testuser1", - # "testuser2", - # "--no-input", - # "--hard", - # ), - # [ - # ( - # users, - # "handler_delete_user", - # {"usernames": ("testuser1", "testuser2"), "soft": False}, - # ) - # ], - # ), - # ( - # ("users", "set"), - # ( - # "testuser1", - # "testuser2", - # "--no-input", - # "--inactive", - # "--upload-quota", - # "35", - # "--no-staff", - # "--superuser", - # "--permission-library", - # "--no-permission-moderation", - # "--no-permission-settings", - # "--password", - # "newpassword", - # ), - # [ - # ( - # users, - # "handler_update_user", - # { - # "usernames": ("testuser1", "testuser2"), - # "kwargs": { - # "is_active": False, - # "upload_quota": 35, - # "is_staff": False, - # "is_superuser": True, - # "permission_library": True, - # "permission_moderation": False, - # "permission_settings": False, - # "password": "newpassword", - # }, - # }, - # ) - # ], - # ), - # ( - # ("albums", "add-tags-from-tracks"), - # tuple(), - # [(library, "handler_add_tags_from_tracks", {"albums": True})], - # ), + ( + ("users", "create"), + ( + "--username", + "testuser", + "--password", + "testpassword", + "--email", + "test@hello.com", + "--upload-quota", + "35", + "--permission", + "library", + "--permission", + "moderation", + "--staff", + "--superuser", + ), + [ + ( + users, + "handler_create_user", + { + "username": "testuser", + "password": "testpassword", + "email": "test@hello.com", + "upload_quota": 35, + "permissions": ("library", "moderation"), + "is_staff": True, + "is_superuser": True, + }, + ) + ], + ), + ( + ("users", "rm"), + ("testuser1", "testuser2", "--no-input"), + [ + ( + users, + "handler_delete_user", + {"usernames": ("testuser1", "testuser2"), "soft": True}, + ) + ], + ), + ( + ("users", "rm"), + ( + "testuser1", + "testuser2", + "--no-input", + "--hard", + ), + [ + ( + users, + "handler_delete_user", + {"usernames": ("testuser1", "testuser2"), "soft": False}, + ) + ], + ), + ( + ("users", "set"), + ( + "testuser1", + "testuser2", + "--no-input", + "--inactive", + "--upload-quota", + "35", + "--no-staff", + "--superuser", + "--permission-library", + "--no-permission-moderation", + "--no-permission-settings", + "--password", + "newpassword", + ), + [ + ( + users, + "handler_update_user", + { + "usernames": ("testuser1", "testuser2"), + "kwargs": { + "is_active": False, + "upload_quota": 35, + "is_staff": False, + "is_superuser": True, + "permission_library": True, + "permission_moderation": False, + "permission_settings": False, + "password": "newpassword", + }, + }, + ) + ], + ), + ( + ("albums", "add-tags-from-tracks"), + tuple(), + [(library, "handler_add_tags_from_tracks", {"albums": True})], + ), ( ("artists", "add-tags-from-tracks"), tuple(), diff --git a/api/tests/conftest.py b/api/tests/conftest.py index d241db5a7..a4249d3e8 100644 --- a/api/tests/conftest.py +++ b/api/tests/conftest.py @@ -391,7 +391,7 @@ def migrator(transactional_db): @pytest.fixture(autouse=True) def rsa_small_key(settings): # smaller size for faster generation, since it's CPU hungry - settings.RSA_KEY_SIZE = 512 + settings.RSA_KEY_SIZE = 1024 @pytest.fixture(autouse=True) diff --git a/api/tests/contrib/listenbrainz/test_listenbrainz.py b/api/tests/contrib/listenbrainz/test_listenbrainz.py index 47ecc5dae..0a27da4da 100644 --- a/api/tests/contrib/listenbrainz/test_listenbrainz.py +++ b/api/tests/contrib/listenbrainz/test_listenbrainz.py @@ -4,7 +4,6 @@ import logging import liblistenbrainz import pytest from django.urls import reverse -from django.utils import timezone from config import plugins from funkwhale_api.contrib.listenbrainz import funkwhale_ready @@ -52,7 +51,8 @@ def test_sync_listenings_from_listenbrainz(factories, mocker, caplog): factories["music.Track"](mbid="f89db7f8-4a1f-4228-a0a1-e7ba028b7476") track = factories["music.Track"](mbid="54c60860-f43d-484e-b691-7ab7ec8de559") factories["history.Listening"]( - creation_date=datetime.datetime.fromtimestamp(1871, timezone.utc), track=track + creation_date=datetime.datetime.fromtimestamp(1871, datetime.timezone.utc), + track=track, ) conf = { diff --git a/api/tests/music/test_activity.py b/api/tests/music/test_activity.py index 813b49599..626b1aea6 100644 --- a/api/tests/music/test_activity.py +++ b/api/tests/music/test_activity.py @@ -19,7 +19,7 @@ def test_upload_import_status_updated_broadcast(factories, mocker): upload = factories["music.Upload"]( import_status="finished", library__actor__user=user ) - signals.upload_import_status_updated.send( + signals.upload_import_status_updated.send_robust( sender=None, upload=upload, old_status="pending", new_status="finished" ) group_send.assert_called_once_with( diff --git a/api/tests/music/test_tasks.py b/api/tests/music/test_tasks.py index 99591ea3c..de058ea41 100644 --- a/api/tests/music/test_tasks.py +++ b/api/tests/music/test_tasks.py @@ -1460,7 +1460,7 @@ def test_tag_albums_from_tracks(queryset_equal_queries, factories, mocker): get_tags_from_foreign_key.assert_called_once_with( ids=expected_queryset.filter(pk__in=[1, 2]), foreign_key_model=models.Track, - foreign_key_attr="album", + foreign_key_attr="albums", ) add_tags_batch.assert_called_once_with( diff --git a/api/tests/subsonic/test_views.py b/api/tests/subsonic/test_views.py index c2878a673..3ffe10078 100644 --- a/api/tests/subsonic/test_views.py +++ b/api/tests/subsonic/test_views.py @@ -646,12 +646,11 @@ def test_search3(f, db, logged_in_api_client, factories): @pytest.mark.parametrize("f", ["json"]) def test_get_playlists(f, db, logged_in_api_client, factories): - logged_in_api_client.user.create_actor() url = reverse("api:subsonic:subsonic-get_playlists") assert url.endswith("getPlaylists") is True playlist1 = factories["playlists.PlaylistTrack"]( - playlist__actor__user=logged_in_api_client.user + playlist__actor=logged_in_api_client.user.create_actor() ).playlist playlist2 = factories["playlists.PlaylistTrack"]( playlist__privacy_level="everyone" @@ -664,7 +663,6 @@ def test_get_playlists(f, db, logged_in_api_client, factories): # no track playlist4 = factories["playlists.Playlist"](privacy_level="everyone") - factories["users.User"](actor=playlist1.actor) factories["users.User"](actor=playlist2.actor) factories["users.User"](actor=playlist3.actor) factories["users.User"](actor=playlist4.actor) @@ -692,7 +690,6 @@ def test_get_playlist(f, db, logged_in_api_client, factories): playlist = factories["playlists.PlaylistTrack"]( playlist__actor__user=logged_in_api_client.user ).playlist - factories["users.User"](actor=playlist.actor) response = logged_in_api_client.get(url, {"f": f, "id": playlist.pk}) diff --git a/api/tests/tags/test_tasks.py b/api/tests/tags/test_tasks.py index 94720fc5b..5fec1d435 100644 --- a/api/tests/tags/test_tasks.py +++ b/api/tests/tags/test_tasks.py @@ -3,8 +3,8 @@ from funkwhale_api.tags import models, tasks def test_get_tags_from_foreign_key(factories): - rock_tag = factories["tags.Tag"](name="Rock") - rap_tag = factories["tags.Tag"](name="Rap") + rock_tag = factories["tags.Tag"](name="rock") + rap_tag = factories["tags.Tag"](name="rap") artist = factories["music.Artist"]() factories["music.Track"].create_batch( 3, artist_credit__artist=artist, set_tags=["rock", "rap"]