@@ -168,6 +168,7 @@ def __contains__(self, project):
168
168
class LifecycleStatus (enum .StrEnum ):
169
169
QuarantineEnter = "quarantine-enter"
170
170
QuarantineExit = "quarantine-exit"
171
+ Archived = "archived"
171
172
172
173
173
174
class Project (SitemapMixin , HasEvents , HasObservations , db .Model ):
@@ -329,64 +330,78 @@ def __acl__(self):
329
330
(Allow , Authenticated , Permissions .SubmitMalwareObservation ),
330
331
]
331
332
332
- # The project has zero or more OIDC publishers registered to it,
333
- # each of which serves as an identity with the ability to upload releases.
334
- for publisher in self .oidc_publishers :
335
- acls .append ((Allow , f"oidc:{ publisher .id } " , [Permissions .ProjectsUpload ]))
333
+ if self .lifecycle_status != LifecycleStatus .Archived :
334
+ # The project has zero or more OIDC publishers registered to it,
335
+ # each of which serves as an identity with the ability to upload releases
336
+ # (only if the project is not archived)
337
+ for publisher in self .oidc_publishers :
338
+ acls .append (
339
+ (Allow , f"oidc:{ publisher .id } " , [Permissions .ProjectsUpload ])
340
+ )
336
341
337
342
# Get all of the users for this project.
338
- query = session .query (Role ).filter (Role .project == self )
339
- query = query .options (orm .lazyload (Role .project ))
340
- query = query .options (orm .lazyload (Role .user ))
343
+ user_query = (
344
+ session .query (Role )
345
+ .filter (Role .project == self )
346
+ .options (orm .lazyload (Role .project ), orm .lazyload (Role .user ))
347
+ )
341
348
permissions = {
342
349
(role .user_id , "Administer" if role .role_name == "Owner" else "Upload" )
343
- for role in query .all ()
350
+ for role in user_query .all ()
344
351
}
345
352
346
353
# Add all of the team members for this project.
347
- query = session .query (TeamProjectRole ).filter (TeamProjectRole .project == self )
348
- query = query .options (orm .lazyload (TeamProjectRole .project ))
349
- query = query .options (orm .lazyload (TeamProjectRole .team ))
350
- for role in query .all ():
354
+ team_query = (
355
+ session .query (TeamProjectRole )
356
+ .filter (TeamProjectRole .project == self )
357
+ .options (
358
+ orm .lazyload (TeamProjectRole .project ),
359
+ orm .lazyload (TeamProjectRole .team ),
360
+ )
361
+ )
362
+ for role in team_query .all ():
351
363
permissions |= {
352
364
(user .id , "Administer" if role .role_name .value == "Owner" else "Upload" )
353
365
for user in role .team .members
354
366
}
355
367
356
368
# Add all organization owners for this project.
357
369
if self .organization :
358
- query = session .query (OrganizationRole ).filter (
359
- OrganizationRole .organization == self .organization ,
360
- OrganizationRole .role_name == OrganizationRoleType .Owner ,
370
+ org_query = (
371
+ session .query (OrganizationRole )
372
+ .filter (
373
+ OrganizationRole .organization == self .organization ,
374
+ OrganizationRole .role_name == OrganizationRoleType .Owner ,
375
+ )
376
+ .options (
377
+ orm .lazyload (OrganizationRole .organization ),
378
+ orm .lazyload (OrganizationRole .user ),
379
+ )
361
380
)
362
- query = query .options (orm .lazyload (OrganizationRole .organization ))
363
- query = query .options (orm .lazyload (OrganizationRole .user ))
364
- permissions |= {(role .user_id , "Administer" ) for role in query .all ()}
381
+ permissions |= {(role .user_id , "Administer" ) for role in org_query .all ()}
365
382
366
383
for user_id , permission_name in sorted (permissions , key = lambda x : (x [1 ], x [0 ])):
367
384
# Disallow Write permissions for Projects in quarantine, allow Upload
368
385
if self .lifecycle_status == LifecycleStatus .QuarantineEnter :
369
- acls .append (
370
- (
371
- Allow ,
372
- f"user:{ user_id } " ,
373
- [Permissions .ProjectsRead , Permissions .ProjectsUpload ],
374
- )
375
- )
386
+ current_permissions = [
387
+ Permissions .ProjectsRead ,
388
+ Permissions .ProjectsUpload ,
389
+ ]
376
390
elif permission_name == "Administer" :
377
- acls .append (
378
- (
379
- Allow ,
380
- f"user:{ user_id } " ,
381
- [
382
- Permissions .ProjectsRead ,
383
- Permissions .ProjectsUpload ,
384
- Permissions .ProjectsWrite ,
385
- ],
386
- )
387
- )
391
+ current_permissions = [
392
+ Permissions .ProjectsRead ,
393
+ Permissions .ProjectsUpload ,
394
+ Permissions .ProjectsWrite ,
395
+ ]
388
396
else :
389
- acls .append ((Allow , f"user:{ user_id } " , [Permissions .ProjectsUpload ]))
397
+ current_permissions = [Permissions .ProjectsUpload ]
398
+
399
+ if self .lifecycle_status == LifecycleStatus .Archived :
400
+ # Disallow upload permissions for archived projects
401
+ current_permissions .remove (Permissions .ProjectsUpload )
402
+
403
+ if current_permissions :
404
+ acls .append ((Allow , f"user:{ user_id } " , current_permissions ))
390
405
return acls
391
406
392
407
@property
@@ -456,10 +471,7 @@ def latest_version(self):
456
471
return (
457
472
orm .object_session (self )
458
473
.query (Release .version , Release .created , Release .is_prerelease )
459
- .filter (
460
- Release .project == self ,
461
- Release .yanked .is_ (False ),
462
- )
474
+ .filter (Release .project == self , Release .yanked .is_ (False ))
463
475
.order_by (Release .is_prerelease .nullslast (), Release ._pypi_ordering .desc ())
464
476
.first ()
465
477
)
@@ -540,8 +552,8 @@ class ReleaseURL(db.Model):
540
552
"Description" ,
541
553
"Description-Content-Type" ,
542
554
"Keywords" ,
543
- "Home-Page" ,
544
- "Download-Url" ,
555
+ "Home-Page" , # Deprecated, but technically permitted by PEP 643
556
+ "Download-Url" , # Deprecated, but technically permitted by PEP 643
545
557
"Author" ,
546
558
"Author-Email" ,
547
559
"Maintainer" ,
@@ -557,6 +569,12 @@ class ReleaseURL(db.Model):
557
569
"Provides-Extra" ,
558
570
"Provides-Dist" ,
559
571
"Obsoletes-Dist" ,
572
+ # Although the following are deprecated fields, they are technically
573
+ # permitted as dynamic by PEP 643
574
+ # https://github.com/pypa/setuptools/issues/4797#issuecomment-2589514950
575
+ "Requires" ,
576
+ "Provides" ,
577
+ "Obsoletes" ,
560
578
name = "release_dynamic_fields" ,
561
579
)
562
580
@@ -617,6 +635,7 @@ def __table_args__(cls): # noqa
617
635
_pypi_ordering : Mapped [int | None ]
618
636
requires_python : Mapped [str | None ] = mapped_column (Text )
619
637
created : Mapped [datetime_now ] = mapped_column ()
638
+ published : Mapped [bool_true ] = mapped_column ()
620
639
621
640
description_id : Mapped [UUID ] = mapped_column (
622
641
ForeignKey ("release_descriptions.id" , onupdate = "CASCADE" , ondelete = "CASCADE" ),
@@ -764,7 +783,7 @@ def urls_by_verify_status(self, *, verified: bool):
764
783
return _urls
765
784
766
785
def verified_user_name_and_repo_name (
767
- self , domains : set [str ], reserved_names : typing .Sequence [str ] | None = None
786
+ self , domains : set [str ], reserved_names : typing .Collection [str ] | None = None
768
787
):
769
788
for _ , url in self .urls_by_verify_status (verified = True ).items ():
770
789
try :
@@ -991,6 +1010,13 @@ def __table_args__(cls): # noqa
991
1010
Index ("journals_version_idx" , "version" ),
992
1011
Index ("journals_submitted_by_idx" , "submitted_by" ),
993
1012
Index ("journals_submitted_date_id_idx" , cls .submitted_date , cls .id ),
1013
+ # Composite index for journals to be able to sort by
1014
+ # `submitted_by`, and `submitted_date` in descending order.
1015
+ Index (
1016
+ "journals_submitted_by_and_reverse_date_idx" ,
1017
+ cls ._submitted_by ,
1018
+ cls .submitted_date .desc (),
1019
+ ),
994
1020
)
995
1021
996
1022
id : Mapped [int ] = mapped_column (primary_key = True )
0 commit comments